@rancher/shell 0.3.17 → 0.3.19
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/translations/en-us.yaml +8 -4
- package/assets/translations/zh-hans.yaml +64 -8
- package/components/AsyncButton.vue +1 -1
- package/components/Inactivity.vue +10 -0
- package/components/LazyImage.vue +2 -2
- package/components/PromptRestore.vue +8 -6
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +4 -2
- package/components/__tests__/PromptRestore.test.ts +142 -0
- package/components/auth/AzureWarning.vue +1 -1
- package/components/auth/RoleDetailEdit.vue +2 -0
- package/components/fleet/FleetResources.vue +3 -64
- package/components/form/FileImageSelector.vue +9 -0
- package/components/form/FileSelector.vue +2 -1
- package/components/form/MatchExpressions.vue +1 -3
- package/components/form/__tests__/FileImageSelector.test.ts +42 -0
- package/components/form/__tests__/FileSelector.test.ts +76 -0
- package/components/formatter/ClusterProvider.vue +3 -1
- package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
- package/components/nav/WindowManager/ContainerShell.vue +60 -36
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
- package/config/labels-annotations.js +2 -1
- package/config/persistentVolume.ts +108 -0
- package/config/product/manager.js +5 -1
- package/config/types.js +2 -0
- package/core/plugin-helpers.js +19 -3
- package/core/types.ts +4 -0
- package/detail/fleet.cattle.io.gitrepo.vue +10 -2
- package/detail/pod.vue +36 -3
- package/detail/workload/index.vue +40 -9
- package/dialog/DiagnosticTimingsDialog.vue +1 -0
- package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
- package/edit/fleet.cattle.io.clustergroup.vue +14 -3
- package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
- package/edit/persistentvolume/index.vue +2 -1
- package/edit/persistentvolume/plugins/csi.vue +3 -1
- package/edit/persistentvolume/plugins/longhorn.vue +12 -12
- package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
- package/edit/storage.k8s.io.storageclass/index.vue +1 -2
- package/edit/ui.cattle.io.navlink.vue +213 -186
- package/layouts/default.vue +1 -1
- package/list/group.principal.vue +1 -1
- package/middleware/authenticated.js +12 -4
- package/mixins/create-edit-view/impl.js +2 -2
- package/models/chart.js +1 -1
- package/models/etcdbackup.js +2 -1
- package/models/fleet.cattle.io.cluster.js +33 -4
- package/models/fleet.cattle.io.gitrepo.js +112 -38
- package/models/management.cattle.io.cluster.js +13 -3
- package/models/management.cattle.io.kontainerdriver.js +14 -0
- package/models/persistentvolume.js +2 -111
- package/models/pod.js +30 -0
- package/models/rke.cattle.io.etcdsnapshot.js +10 -7
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +74 -25
- package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +1 -1
- package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
- package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
- package/pages/c/_cluster/settings/brand.vue +11 -8
- package/pages/c/_cluster/uiplugins/index.vue +9 -4
- package/pages/diagnostic.vue +5 -3
- package/pages/home.vue +1 -1
- package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
- package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
- package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +4 -0
- package/plugins/steve/__tests__/getters.spec.ts +93 -0
- package/plugins/steve/getters.js +21 -1
- package/plugins/steve/subscribe.js +1 -3
- package/rancher-components/components/BadgeState/BadgeState.spec.ts +12 -0
- package/rancher-components/components/BadgeState/BadgeState.vue +111 -0
- package/rancher-components/components/BadgeState/index.ts +1 -0
- package/rancher-components/components/Banner/Banner.test.ts +63 -0
- package/rancher-components/components/Banner/Banner.vue +244 -0
- package/rancher-components/components/Banner/index.ts +1 -0
- package/rancher-components/components/Card/Card.test.ts +37 -0
- package/rancher-components/components/Card/Card.vue +167 -0
- package/rancher-components/components/Card/index.ts +1 -0
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +68 -0
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +420 -0
- package/rancher-components/components/Form/Checkbox/index.ts +1 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +23 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +355 -0
- package/rancher-components/components/Form/LabeledInput/index.ts +1 -0
- package/rancher-components/components/Form/Radio/RadioButton.test.ts +31 -0
- package/rancher-components/components/Form/Radio/RadioButton.vue +287 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +254 -0
- package/rancher-components/components/Form/Radio/index.ts +2 -0
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +170 -0
- package/rancher-components/components/Form/TextArea/index.ts +1 -0
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +94 -0
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +149 -0
- package/rancher-components/components/Form/ToggleSwitch/index.ts +1 -0
- package/rancher-components/components/Form/index.ts +5 -0
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +151 -0
- package/rancher-components/components/LabeledTooltip/index.ts +1 -0
- package/rancher-components/components/StringList/StringList.test.ts +484 -0
- package/rancher-components/components/StringList/StringList.vue +611 -0
- package/rancher-components/components/StringList/index.ts +1 -0
- package/scripts/extension/publish +54 -14
- package/scripts/typegen.sh +10 -2
- package/store/index.js +1 -3
- package/store/store-types.js +2 -0
- package/types/api.d.ts +1 -0
- package/types/fleet.d.ts +1 -0
- package/types/shell/index.d.ts +696 -2
- package/types/userPreferences.d.ts +1 -1
- package/utils/__mocks__/socket.js +21 -0
- package/utils/grafana.js +23 -11
- package/utils/selector.js +2 -1
- package/utils/socket.js +1 -0
- package/utils/validators/formRules/index.ts +3 -3
- package/plugins/steve/urloptions.js +0 -47
|
@@ -81,6 +81,9 @@ export default {
|
|
|
81
81
|
],
|
|
82
82
|
|
|
83
83
|
async fetch() {
|
|
84
|
+
this.errors = [];
|
|
85
|
+
// IMPORTANT! Any exception thrown before this.value is set will result in an empty page
|
|
86
|
+
|
|
84
87
|
/*
|
|
85
88
|
fetchChart is defined in shell/mixins. It first checks the URL
|
|
86
89
|
query for an app name and namespace. It uses those values to check
|
|
@@ -91,23 +94,43 @@ export default {
|
|
|
91
94
|
it checks for target name and namespace values defined in the
|
|
92
95
|
Helm chart itself.
|
|
93
96
|
*/
|
|
94
|
-
|
|
97
|
+
try {
|
|
98
|
+
await this.fetchChart();
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.warn('Unable to fetch chart: ', e); // eslint-disable-line no-console
|
|
101
|
+
}
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
try {
|
|
104
|
+
await this.fetchAutoInstallInfo();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.warn('Unable to determine if other charts require install: ', e); // eslint-disable-line no-console
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
// If the chart doesn't contain system `systemDefaultRegistry` properties there's no point applying them
|
|
100
110
|
if (this.showCustomRegistry) {
|
|
101
111
|
// Note: Cluster scoped registry is only supported for node driver clusters
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
try {
|
|
113
|
+
this.clusterRegistry = await this.getClusterRegistry();
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.warn('Unable to get cluster registry: ', e); // eslint-disable-line no-console
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
this.globalRegistry = await this.getGlobalRegistry();
|
|
120
|
+
} catch (e) {
|
|
121
|
+
console.warn('Unable to get global registry: ', e); // eslint-disable-line no-console
|
|
122
|
+
}
|
|
104
123
|
this.defaultRegistrySetting = this.clusterRegistry || this.globalRegistry;
|
|
105
124
|
}
|
|
106
125
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
try {
|
|
127
|
+
this.serverUrlSetting = await this.$store.dispatch('management/find', {
|
|
128
|
+
type: MANAGEMENT.SETTING,
|
|
129
|
+
id: 'server-url'
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error('Unable to fetch `server-url` setting: ', e); // eslint-disable-line no-console
|
|
133
|
+
}
|
|
111
134
|
|
|
112
135
|
/*
|
|
113
136
|
Figure out the namespace where the chart is
|
|
@@ -137,20 +160,38 @@ export default {
|
|
|
137
160
|
}
|
|
138
161
|
|
|
139
162
|
/* Check if the app is deprecated. */
|
|
140
|
-
|
|
163
|
+
try {
|
|
164
|
+
this.legacyApp = this.existing ? await this.existing.deployedAsLegacy() : false;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
this.legacyApp = false;
|
|
167
|
+
console.warn('Unable to determine if existing install is a legacy app: ', e); // eslint-disable-line no-console
|
|
168
|
+
}
|
|
141
169
|
|
|
142
170
|
/* Check if the app is a multicluster deprecated app.
|
|
143
171
|
(Multicluster apps were replaced by Fleet.) */
|
|
144
|
-
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
this.mcapp = this.existing ? await this.existing.deployedAsMultiCluster() : false;
|
|
175
|
+
} catch (e) {
|
|
176
|
+
this.mcapp = false;
|
|
177
|
+
console.warn('Unable to determine if existing install is a mc app: ', e); // eslint-disable-line no-console
|
|
178
|
+
}
|
|
145
179
|
|
|
146
180
|
/* The form state is intialized as a chartInstallAction resource. */
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
181
|
+
try {
|
|
182
|
+
this.value = await this.$store.dispatch('cluster/create', {
|
|
183
|
+
type: 'chartInstallAction',
|
|
184
|
+
metadata: {
|
|
185
|
+
namespace: this.forceNamespace || this.$store.getters['defaultNamespace'],
|
|
186
|
+
name: this.existing?.spec?.name || this.query.appName || '',
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.error('Unable to create object of type `chartInstallAction`: ', e); // eslint-disable-line no-console
|
|
191
|
+
|
|
192
|
+
// Nothing's going to work without a `value`. See https://github.com/rancher/dashboard/issues/9452 to handle this and other catches.
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
154
195
|
|
|
155
196
|
/* Logic for when the Helm chart is not already installed */
|
|
156
197
|
if ( !this.existing) {
|
|
@@ -803,12 +844,19 @@ export default {
|
|
|
803
844
|
|
|
804
845
|
if (hasPermissionToSeeProvCluster) {
|
|
805
846
|
const mgmCluster = this.$store.getters['currentCluster'];
|
|
806
|
-
const
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
847
|
+
const provClusterId = mgmCluster?.provClusterId;
|
|
848
|
+
let provCluster;
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
provCluster = provClusterId ? await this.$store.dispatch('management/find', {
|
|
852
|
+
type: CAPI.RANCHER_CLUSTER,
|
|
853
|
+
id: provClusterId
|
|
854
|
+
}) : {};
|
|
855
|
+
} catch (e) {
|
|
856
|
+
console.error(`Unable to fetch prov cluster '${ provClusterId }': `, e); // eslint-disable-line no-console
|
|
857
|
+
}
|
|
810
858
|
|
|
811
|
-
if (provCluster
|
|
859
|
+
if (provCluster?.isRke2) { // isRke2 returns true for both RKE2 and K3s clusters.
|
|
812
860
|
const agentConfig = provCluster.spec?.rkeConfig?.machineSelectorConfig?.find((x) => !x.machineLabelSelector).config;
|
|
813
861
|
|
|
814
862
|
// If a cluster scoped registry exists,
|
|
@@ -819,10 +867,11 @@ export default {
|
|
|
819
867
|
return clusterRegistry;
|
|
820
868
|
}
|
|
821
869
|
}
|
|
822
|
-
|
|
870
|
+
|
|
871
|
+
if (provCluster?.isRke1) {
|
|
823
872
|
// For RKE1 clusters, the cluster scoped private registry is on the management
|
|
824
873
|
// cluster, not the provisioning cluster.
|
|
825
|
-
const rke1Registries = mgmCluster
|
|
874
|
+
const rke1Registries = mgmCluster?.spec?.rancherKubernetesEngineConfig?.privateRegistries;
|
|
826
875
|
|
|
827
876
|
if (rke1Registries?.length > 0) {
|
|
828
877
|
const defaultRegistry = rke1Registries.find((registry) => {
|
|
@@ -6,7 +6,7 @@ import { NORMAN } from '@shell/config/types';
|
|
|
6
6
|
import { _VIEW, _EDIT } from '@shell/config/query-params';
|
|
7
7
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
8
8
|
import { NAME } from '@shell/config/product/auth';
|
|
9
|
-
import { BLANK_CLUSTER } from '@shell/store';
|
|
9
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
10
10
|
|
|
11
11
|
export default {
|
|
12
12
|
components: {
|
|
@@ -6,7 +6,7 @@ import ResourceTable from '@shell/components/ResourceTable';
|
|
|
6
6
|
import Loading from '@shell/components/Loading';
|
|
7
7
|
import { SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
|
|
8
8
|
import { NAME } from '@shell/config/product/auth';
|
|
9
|
-
import { BLANK_CLUSTER } from '@shell/store';
|
|
9
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
10
10
|
|
|
11
11
|
const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
|
|
12
12
|
const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
|
|
@@ -103,7 +103,7 @@ export default {
|
|
|
103
103
|
`Determine etcd metrics`
|
|
104
104
|
);
|
|
105
105
|
|
|
106
|
-
if (this.currentCluster.isLocal) {
|
|
106
|
+
if (this.currentCluster.isLocal && this.$store.getters['management/schemaFor'](MANAGEMENT.NODE)) {
|
|
107
107
|
this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -4,7 +4,7 @@ import ColorInput from '@shell/components/form/ColorInput';
|
|
|
4
4
|
import TypeDescription from '@shell/components/TypeDescription';
|
|
5
5
|
|
|
6
6
|
import { Checkbox } from '@components/Form/Checkbox';
|
|
7
|
-
import
|
|
7
|
+
import FileImageSelector from '@shell/components/form/FileImageSelector';
|
|
8
8
|
import SimpleBox from '@shell/components/SimpleBox';
|
|
9
9
|
import Loading from '@shell/components/Loading';
|
|
10
10
|
import AsyncButton from '@shell/components/AsyncButton';
|
|
@@ -23,7 +23,7 @@ export default {
|
|
|
23
23
|
layout: 'authenticated',
|
|
24
24
|
|
|
25
25
|
components: {
|
|
26
|
-
LabeledInput, Checkbox,
|
|
26
|
+
LabeledInput, Checkbox, FileImageSelector, Loading, SimpleBox, AsyncButton, Banner, ColorInput, TypeDescription
|
|
27
27
|
},
|
|
28
28
|
|
|
29
29
|
async fetch() {
|
|
@@ -222,14 +222,15 @@ export default {
|
|
|
222
222
|
>
|
|
223
223
|
<div class="col logo-container span-6">
|
|
224
224
|
<div class="mb-10">
|
|
225
|
-
<
|
|
225
|
+
<FileImageSelector
|
|
226
226
|
:byte-limit="20000"
|
|
227
227
|
:read-as-data-url="true"
|
|
228
228
|
class="role-secondary"
|
|
229
229
|
:label="t('branding.logos.uploadLight')"
|
|
230
230
|
:mode="mode"
|
|
231
|
+
accept="image/jpeg,image/png,image/svg+xml"
|
|
231
232
|
@error="setError"
|
|
232
|
-
@
|
|
233
|
+
@input="updateLogo($event, 'uiLogoLight')"
|
|
233
234
|
/>
|
|
234
235
|
</div>
|
|
235
236
|
<SimpleBox
|
|
@@ -245,14 +246,15 @@ export default {
|
|
|
245
246
|
</div>
|
|
246
247
|
<div class="col logo-container span-6">
|
|
247
248
|
<div class="mb-10">
|
|
248
|
-
<
|
|
249
|
+
<FileImageSelector
|
|
249
250
|
:byte-limit="20000"
|
|
250
251
|
:read-as-data-url="true"
|
|
251
252
|
class="role-secondary"
|
|
252
253
|
:label="t('branding.logos.uploadDark')"
|
|
253
254
|
:mode="mode"
|
|
255
|
+
accept="image/jpeg,image/png,image/svg+xml"
|
|
254
256
|
@error="setError"
|
|
255
|
-
@
|
|
257
|
+
@input="updateLogo($event, 'uiLogoDark')"
|
|
256
258
|
/>
|
|
257
259
|
</div>
|
|
258
260
|
<SimpleBox
|
|
@@ -289,14 +291,15 @@ export default {
|
|
|
289
291
|
>
|
|
290
292
|
<div class="col logo-container span-12">
|
|
291
293
|
<div class="mb-10">
|
|
292
|
-
<
|
|
294
|
+
<FileImageSelector
|
|
293
295
|
:byte-limit="20000"
|
|
294
296
|
:read-as-data-url="true"
|
|
295
297
|
class="role-secondary"
|
|
296
298
|
:label="t('branding.favicon.upload')"
|
|
297
299
|
:mode="mode"
|
|
300
|
+
accept="image/jpeg,image/png,image/svg+xml"
|
|
298
301
|
@error="setError"
|
|
299
|
-
@
|
|
302
|
+
@input="updateLogo($event, 'uiFavicon')"
|
|
300
303
|
/>
|
|
301
304
|
</div>
|
|
302
305
|
<SimpleBox v-if="uiFavicon">
|
|
@@ -541,6 +541,7 @@ export default {
|
|
|
541
541
|
},
|
|
542
542
|
|
|
543
543
|
showAddExtensionReposDialog() {
|
|
544
|
+
this.updateAddReposSetting();
|
|
544
545
|
this.refreshCharts(true);
|
|
545
546
|
this.$refs.addExtensionReposDialog.showDialog();
|
|
546
547
|
},
|
|
@@ -745,14 +746,13 @@ export default {
|
|
|
745
746
|
<Banner
|
|
746
747
|
v-if="showAddReposBanner"
|
|
747
748
|
color="warning"
|
|
748
|
-
class="mb-20"
|
|
749
|
-
:closable="true"
|
|
749
|
+
class="add-repos-banner mb-20"
|
|
750
750
|
data-testid="extensions-new-repos-banner"
|
|
751
|
-
@close="updateAddReposSetting"
|
|
752
751
|
>
|
|
753
752
|
<span>{{ t('plugins.addRepos.banner', {}, true) }}</span>
|
|
754
753
|
<button
|
|
755
754
|
class="ml-10 btn btn-sm role-primary"
|
|
755
|
+
data-testid="extensions-new-repos-banner-action-btn"
|
|
756
756
|
@click="showAddExtensionReposDialog()"
|
|
757
757
|
>
|
|
758
758
|
{{ t('plugins.addRepos.bannerBtn') }}
|
|
@@ -1237,11 +1237,16 @@ export default {
|
|
|
1237
1237
|
}
|
|
1238
1238
|
}
|
|
1239
1239
|
}
|
|
1240
|
-
|
|
1241
1240
|
::v-deep .checkbox-label {
|
|
1242
1241
|
font-weight: normal !important;
|
|
1243
1242
|
}
|
|
1244
1243
|
|
|
1244
|
+
::v-deep .add-repos-banner .banner__content {
|
|
1245
|
+
display: flex;
|
|
1246
|
+
justify-content: space-between;
|
|
1247
|
+
align-items: center;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1245
1250
|
@media screen and (max-width: 1200px) {
|
|
1246
1251
|
.plugin-list {
|
|
1247
1252
|
.plugin {
|
package/pages/diagnostic.vue
CHANGED
|
@@ -162,9 +162,8 @@ export default {
|
|
|
162
162
|
},
|
|
163
163
|
|
|
164
164
|
downloadData(btnCb) {
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const fileName = `rancher-diagnostic-data-${ date }-${ time.replaceAll(':', '_') }.json`;
|
|
165
|
+
// simplify filename due to e2e tests
|
|
166
|
+
const fileName = 'rancher-diagnostic-data.json';
|
|
168
167
|
const data = {
|
|
169
168
|
systemInformation: this.systemInformation,
|
|
170
169
|
logs: this.latestLogs,
|
|
@@ -235,6 +234,7 @@ export default {
|
|
|
235
234
|
'aws',
|
|
236
235
|
'digitalocean',
|
|
237
236
|
'linode',
|
|
237
|
+
'targetRoute', // contains circular references, isn't useful (added later to store)
|
|
238
238
|
];
|
|
239
239
|
|
|
240
240
|
const clearListsKeys = [
|
|
@@ -316,10 +316,12 @@ export default {
|
|
|
316
316
|
<AsyncButton
|
|
317
317
|
mode="timing"
|
|
318
318
|
class="mr-20"
|
|
319
|
+
data-testid="diagnostics-generate-response-times"
|
|
319
320
|
@click="gatherResponseTimes"
|
|
320
321
|
/>
|
|
321
322
|
<AsyncButton
|
|
322
323
|
mode="diagnostic"
|
|
324
|
+
data-testid="diagnostics-download-diagnostic-package"
|
|
323
325
|
@click="promptDownload"
|
|
324
326
|
/>
|
|
325
327
|
</div>
|
package/pages/home.vue
CHANGED
|
@@ -17,7 +17,7 @@ import { getVersionInfo, readReleaseNotes, markReadReleaseNotes, markSeenRelease
|
|
|
17
17
|
import PageHeaderActions from '@shell/mixins/page-actions';
|
|
18
18
|
import { getVendor } from '@shell/config/private-label';
|
|
19
19
|
import { mapFeature, MULTI_CLUSTER } from '@shell/store/features';
|
|
20
|
-
import { BLANK_CLUSTER } from '@shell/store';
|
|
20
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
21
21
|
import { filterOnlyKubernetesClusters, filterHiddenLocalCluster } from '@shell/utils/cluster';
|
|
22
22
|
|
|
23
23
|
import { RESET_CARDS_ACTION, SET_LOGIN_ACTION } from '@shell/config/page-actions';
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import _actions from '@shell/plugins/dashboard-store/actions';
|
|
2
|
+
|
|
3
|
+
const { findAll } = _actions;
|
|
4
|
+
|
|
5
|
+
describe('dashboard-store: actions', () => {
|
|
6
|
+
const setupContext = () => {
|
|
7
|
+
const commit = jest.fn();
|
|
8
|
+
const dispatch = jest.fn((...args) => {
|
|
9
|
+
switch (args[0]) {
|
|
10
|
+
case 'request':
|
|
11
|
+
return { data: ['requestData'] };
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
const state = { config: { namespace: 'unitTest' } };
|
|
15
|
+
const getters = {
|
|
16
|
+
normalizeType: jest.fn(() => 'getters.normalizeType'),
|
|
17
|
+
typeRegistered: jest.fn(() => false),
|
|
18
|
+
haveAll: jest.fn(() => false),
|
|
19
|
+
haveAllNamespace: jest.fn(() => false),
|
|
20
|
+
all: jest.fn(() => 'getters.all'),
|
|
21
|
+
urlFor: jest.fn(() => 'getters.urlFor'), // we're not testing the urlFor getter so we don't need to do anything with opt here
|
|
22
|
+
};
|
|
23
|
+
const rootGetters = {
|
|
24
|
+
'type-map/optionsFor': jest.fn(),
|
|
25
|
+
'auth/fromHeader': 'foo'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// we're not testing function output based off of state or getter inputs here since they are dependencies and should be tested independently
|
|
29
|
+
return {
|
|
30
|
+
state,
|
|
31
|
+
getters,
|
|
32
|
+
rootGetters,
|
|
33
|
+
commit,
|
|
34
|
+
dispatch
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const standardAssertions = {
|
|
39
|
+
returnsPromise:
|
|
40
|
+
{
|
|
41
|
+
assertionLabel: 'returns a promise',
|
|
42
|
+
valueGetter: ({ findAllPromise }) => typeof findAllPromise.then,
|
|
43
|
+
valueExpected: 'function'
|
|
44
|
+
},
|
|
45
|
+
callsAll: {
|
|
46
|
+
assertionLabel: 'calls the "all" getter with the normalizedType',
|
|
47
|
+
valueGetter: ({ getters }) => getters.all.mock.calls[0][0],
|
|
48
|
+
valueExpected: 'getters.normalizeType'
|
|
49
|
+
},
|
|
50
|
+
returnsFromAll: {
|
|
51
|
+
assertionLabel: 'returns the value expected from the "all" getter',
|
|
52
|
+
valueGetter: ({ findAllReturnValue }) => findAllReturnValue,
|
|
53
|
+
valueExpected: 'getters.all'
|
|
54
|
+
},
|
|
55
|
+
firstDispatchAction: {
|
|
56
|
+
assertionLabel: 'first dispatch should be the "request" action',
|
|
57
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[0][0],
|
|
58
|
+
valueExpected: 'request'
|
|
59
|
+
},
|
|
60
|
+
firstDispatchParams: {
|
|
61
|
+
assertionLabel: 'first dispatch parameters should be provided a normalized type and a url, streaming, and "metadata.managedFields" excluded under opt',
|
|
62
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[0][1],
|
|
63
|
+
valueExpected: {
|
|
64
|
+
type: 'getters.normalizeType',
|
|
65
|
+
opt: {
|
|
66
|
+
url: 'getters.urlFor',
|
|
67
|
+
stream: true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
assertionMethod: 'toMatchObject'
|
|
71
|
+
},
|
|
72
|
+
secondDispatchAction: {
|
|
73
|
+
assertionLabel: 'second dispatch should be the "watch" action',
|
|
74
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[1][0],
|
|
75
|
+
valueExpected: 'watch'
|
|
76
|
+
},
|
|
77
|
+
secondDispatchParams: {
|
|
78
|
+
assertionLabel: 'second dispatch parameters should have a normalized type and force set to false',
|
|
79
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls[1][1],
|
|
80
|
+
valueExpected: { type: 'getters.normalizeType', force: false },
|
|
81
|
+
assertionMethod: 'toMatchObject'
|
|
82
|
+
},
|
|
83
|
+
countDispatches: {
|
|
84
|
+
assertionLabel: 'should only make two dispatches',
|
|
85
|
+
valueGetter: ({ dispatch }) => dispatch.mock.calls,
|
|
86
|
+
valueExpected: 2,
|
|
87
|
+
assertionMethod: 'toHaveLength'
|
|
88
|
+
},
|
|
89
|
+
firstCommitMutation: {
|
|
90
|
+
assertionLabel: 'first commit should be the "registerType" mutation',
|
|
91
|
+
valueGetter: ({ commit }) => commit.mock.calls[0][0],
|
|
92
|
+
valueExpected: 'registerType'
|
|
93
|
+
},
|
|
94
|
+
firstCommitParams: {
|
|
95
|
+
assertionLabel: 'first commit parameter should be a normalized type',
|
|
96
|
+
valueGetter: ({ commit }) => commit.mock.calls[0][1],
|
|
97
|
+
valueExpected: 'getters.normalizeType'
|
|
98
|
+
},
|
|
99
|
+
secondCommitMutation: {
|
|
100
|
+
assertionLabel: 'second commit should be the "loadAll" mutation',
|
|
101
|
+
valueGetter: ({ commit }) => commit.mock.calls[1][0],
|
|
102
|
+
valueExpected: 'loadAll'
|
|
103
|
+
},
|
|
104
|
+
secondCommitParams: {
|
|
105
|
+
assertionLabel: 'second commit parameters should have a normalized type, ctx.state.config.namespace, data returned by request, and skipHaveAll set to false',
|
|
106
|
+
valueGetter: ({ commit }) => commit.mock.calls[1][1],
|
|
107
|
+
valueExpected: {
|
|
108
|
+
type: 'getters.normalizeType',
|
|
109
|
+
ctx: { state: { config: { namespace: 'unitTest' } } },
|
|
110
|
+
data: ['requestData'],
|
|
111
|
+
skipHaveAll: false,
|
|
112
|
+
},
|
|
113
|
+
assertionMethod: 'toMatchObject'
|
|
114
|
+
},
|
|
115
|
+
countCommits: {
|
|
116
|
+
assertionLabel: 'should only make two commits',
|
|
117
|
+
valueGetter: ({ commit }) => commit.mock.calls,
|
|
118
|
+
valueExpected: 2,
|
|
119
|
+
assertionMethod: 'toHaveLength'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
describe('dashboard-store > actions > findAll', () => {
|
|
125
|
+
describe('called without a cache for the type in the second param', () => {
|
|
126
|
+
const {
|
|
127
|
+
dispatch, commit, getters, rootGetters, state
|
|
128
|
+
} = setupContext();
|
|
129
|
+
|
|
130
|
+
const findAllPromise = findAll(
|
|
131
|
+
{
|
|
132
|
+
dispatch, commit, getters, rootGetters, state
|
|
133
|
+
},
|
|
134
|
+
{ type: 'type' }
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const assertionChain = [
|
|
138
|
+
standardAssertions.returnsPromise,
|
|
139
|
+
standardAssertions.callsAll,
|
|
140
|
+
standardAssertions.returnsFromAll,
|
|
141
|
+
standardAssertions.firstDispatchAction,
|
|
142
|
+
standardAssertions.firstDispatchParams,
|
|
143
|
+
standardAssertions.secondDispatchAction,
|
|
144
|
+
standardAssertions.secondDispatchParams,
|
|
145
|
+
standardAssertions.countDispatches,
|
|
146
|
+
standardAssertions.firstCommitMutation,
|
|
147
|
+
standardAssertions.firstCommitParams,
|
|
148
|
+
standardAssertions.secondCommitMutation,
|
|
149
|
+
standardAssertions.secondCommitParams,
|
|
150
|
+
standardAssertions.countCommits
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
it.each(assertionChain)(
|
|
154
|
+
'$assertionLabel',
|
|
155
|
+
async({ valueGetter, valueExpected, assertionMethod = 'toBe' }) => {
|
|
156
|
+
const findAllReturnValue = await findAllPromise;
|
|
157
|
+
|
|
158
|
+
expect(valueGetter({
|
|
159
|
+
findAllPromise, findAllReturnValue, getters, dispatch, commit
|
|
160
|
+
}))[assertionMethod](valueExpected);
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import getters, { urlFor } from '@shell/plugins/dashboard-store/getters';
|
|
2
|
+
|
|
3
|
+
const { urlOptions } = getters;
|
|
4
|
+
|
|
5
|
+
describe('dashboard-store: getters', () => {
|
|
6
|
+
describe('dashboard-store > getters > exported function: urlFor', () => {
|
|
7
|
+
// we're not testing function output based off of state or getter inputs here since they are dependencies
|
|
8
|
+
const state = { config: { baseUrl: 'protocol' } };
|
|
9
|
+
const getters = {
|
|
10
|
+
normalizeType: (type) => type,
|
|
11
|
+
schemaFor: (type) => {
|
|
12
|
+
if (type === 'typeFoo') {
|
|
13
|
+
return { links: { collection: 'urlFoo' } };
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
// this has its own tests so it just returns the input string
|
|
17
|
+
urlOptions: (string) => string
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const urlForGetter = urlFor(state, getters);
|
|
21
|
+
|
|
22
|
+
it('expects urlFor to return a function', () => {
|
|
23
|
+
expect(typeof urlFor(state, getters)).toBe('function');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('expects function returned by urlFor to return a string', () => {
|
|
27
|
+
expect(urlForGetter('typeFoo')).toBe('protocol/urlFoo');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('expects function returned by urlFor to return a the url supplied as opt.url directly', () => {
|
|
31
|
+
expect(urlForGetter('typeFoo', null, { url: 'urlBar' })).toBe('protocol/urlBar');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('expects function returned by urlFor to return a the url to not receive an additional protocol if relative url', () => {
|
|
35
|
+
expect(urlForGetter('typeFoo', null, { url: '/urlBar' })).toBe('/urlBar');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('expects function returned by urlFor to return a the url to to not receive an additional protocol if the url starts with "http"', () => {
|
|
39
|
+
expect(urlForGetter('typeFoo', null, { url: 'http urlBar' })).toBe('http urlBar');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('expects function returned by urlFor to throw an error if passed an invalid type', () => {
|
|
43
|
+
expect(() => urlForGetter('typeBaz')).toThrow('Unknown schema for type: typeBaz');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('expects function returned by urlFor to append an id to the url if one is supplied', () => {
|
|
47
|
+
expect(urlForGetter('typeFoo', 'idBar')).toBe('protocol/urlFoo/idBar');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('dashboard-store > getters > urlOptions', () => {
|
|
51
|
+
// we're not testing function output based off of state or getter inputs here since they are dependencies
|
|
52
|
+
const state = { config: { baseUrl: 'protocol' } };
|
|
53
|
+
const getters = {
|
|
54
|
+
normalizeType: (type) => type,
|
|
55
|
+
schemaFor: (type) => {
|
|
56
|
+
if (type === 'typeFoo') {
|
|
57
|
+
return { links: { collection: 'urlFoo' } };
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
// this has its own tests so it just returns the input string
|
|
61
|
+
urlOptions: (string) => string
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const urlOptionsGetter = urlOptions();
|
|
65
|
+
|
|
66
|
+
it('expects urlOptions to return a function', () => {
|
|
67
|
+
expect(typeof urlOptions(state, getters)).toBe('function');
|
|
68
|
+
});
|
|
69
|
+
it('returns undefined when called without params', () => {
|
|
70
|
+
expect(urlOptionsGetter()).toBeUndefined();
|
|
71
|
+
});
|
|
72
|
+
it('returns an unmodified string when called without options', () => {
|
|
73
|
+
expect(urlOptionsGetter('foo')).toBe('foo');
|
|
74
|
+
});
|
|
75
|
+
it('returns an unmodified string when called with options that are not accounted for', () => {
|
|
76
|
+
expect(urlOptionsGetter('foo', { bar: 'baz' })).toBe('foo');
|
|
77
|
+
});
|
|
78
|
+
it('returns an unmodified stringif a single filter statement is applied', () => {
|
|
79
|
+
expect(urlOptionsGetter('foo', { filter: { bar: 'baz' } })).toBe('foo');
|
|
80
|
+
});
|
|
81
|
+
it('returns an unmodified string if a single filter statement is applied', () => {
|
|
82
|
+
expect(urlOptionsGetter('foo', { filter: { bar: 'baz', far: 'faz' } })).toBe('foo');
|
|
83
|
+
});
|
|
84
|
+
it('returns an unmodified string if excludeFields is a single element array with the string "bar"', () => {
|
|
85
|
+
expect(urlOptionsGetter('/v1/foo', { excludeFields: ['bar'] })).toBe('/v1/foo');
|
|
86
|
+
});
|
|
87
|
+
it('returns an unmodified string if excludeFields is an array but the URL doesnt include the "/v1/ string"', () => {
|
|
88
|
+
expect(urlOptionsGetter('foo', { excludeFields: ['bar'] })).toBe('foo');
|
|
89
|
+
});
|
|
90
|
+
it('returns an unmodified string if a limit is provided', () => {
|
|
91
|
+
expect(urlOptionsGetter('foo', { limit: 10 })).toBe('foo');
|
|
92
|
+
});
|
|
93
|
+
it('returns an unmodified string if the sort option is provided', () => {
|
|
94
|
+
expect(urlOptionsGetter('foo', { sortBy: 'bar' })).toBe('foo');
|
|
95
|
+
});
|
|
96
|
+
it('returns an unmodified string if the sort option is provided and an order if sortOrder is provided', () => {
|
|
97
|
+
expect(urlOptionsGetter('foo', { sortBy: 'bar', sortOrder: 'baz' })).toBe('foo');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -256,8 +256,8 @@ describe('dashboard-store: mutations', () => {
|
|
|
256
256
|
{
|
|
257
257
|
ctx,
|
|
258
258
|
batch: {
|
|
259
|
-
[POD]:
|
|
260
|
-
[
|
|
259
|
+
[POD]: { [pod.id]: pod },
|
|
260
|
+
[WORKLOAD_TYPES.DEPLOYMENT]: { [deployment.id]: deployment }
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
],
|
|
@@ -1464,6 +1464,10 @@ export default class Resource {
|
|
|
1464
1464
|
}
|
|
1465
1465
|
|
|
1466
1466
|
async saveYaml(yaml) {
|
|
1467
|
+
this._saveYaml(yaml);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
async _saveYaml(yaml) {
|
|
1467
1471
|
/* Multipart support, but need to know the right cluster and work for management store
|
|
1468
1472
|
and "apply" seems to only work for create, not update.
|
|
1469
1473
|
|