@rancher/shell 0.3.17 → 0.3.18
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 +7 -5
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +4 -2
- package/components/__tests__/PromptRestore.test.ts +72 -0
- package/components/auth/AzureWarning.vue +1 -1
- 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/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 -187
- 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/fleet.cattle.io.cluster.js +33 -4
- package/models/fleet.cattle.io.gitrepo.js +112 -38
- 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/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/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/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 +695 -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/validators/formRules/index.ts +3 -3
- package/plugins/steve/urloptions.js +0 -47
package/config/types.js
CHANGED
package/core/plugin-helpers.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ActionLocation, CardLocation, ExtensionPoint } from '@shell/core/types';
|
|
2
2
|
import { isMac } from '@shell/utils/platform';
|
|
3
3
|
import { ucFirst, randomStr } from '@shell/utils/string';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
_EDIT, _CONFIG, _DETAIL, _LIST, _CREATE
|
|
6
|
+
} from '@shell/config/query-params';
|
|
5
7
|
import { getProductFromRoute } from '@shell/middleware/authenticated';
|
|
6
8
|
import { isEqual } from '@shell/utils/object';
|
|
7
9
|
|
|
@@ -21,15 +23,18 @@ function checkRouteProduct({ name, params, query }, locationConfigParam) {
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
function checkRouteMode({ name, query }, locationConfigParam) {
|
|
24
|
-
if (locationConfigParam === _EDIT && query.mode && query.mode === _EDIT) {
|
|
26
|
+
if (locationConfigParam === _EDIT && query.mode && query.mode === _EDIT && !query.as) {
|
|
25
27
|
return true;
|
|
26
28
|
} else if (locationConfigParam === _CONFIG && query.as && query.as === _CONFIG) {
|
|
27
29
|
return true;
|
|
28
|
-
} else if (locationConfigParam === _DETAIL && name.includes('-id')) {
|
|
30
|
+
} else if (locationConfigParam === _DETAIL && !query.as && name.includes('-id') && (!query.mode || query?.mode !== _EDIT)) {
|
|
29
31
|
return true;
|
|
30
32
|
// alias to target all list views
|
|
31
33
|
} else if (locationConfigParam === _LIST && !name.includes('-id') && name.includes('-resource')) {
|
|
32
34
|
return true;
|
|
35
|
+
// alias to target create views
|
|
36
|
+
} else if (locationConfigParam === _CREATE && name.endsWith('-create')) {
|
|
37
|
+
return true;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
return false;
|
|
@@ -52,6 +57,7 @@ function checkExtensionRouteBinding($route, locationConfig, context) {
|
|
|
52
57
|
'cluster',
|
|
53
58
|
'id',
|
|
54
59
|
'mode',
|
|
60
|
+
'path',
|
|
55
61
|
// url query params
|
|
56
62
|
'queryParam',
|
|
57
63
|
// Custom context specific params provided by the extension, not to be confused with location params
|
|
@@ -79,8 +85,18 @@ function checkExtensionRouteBinding($route, locationConfig, context) {
|
|
|
79
85
|
} else if (param === 'context') {
|
|
80
86
|
// Need all keys and values to match
|
|
81
87
|
res = isEqual(locationConfigParam, context);
|
|
88
|
+
// evaluate queryParam in route
|
|
82
89
|
} else if (param === 'queryParam') {
|
|
83
90
|
res = isEqual(locationConfigParam, $route.query);
|
|
91
|
+
// evaluate path in route
|
|
92
|
+
} else if (param === 'path' && locationConfigParam.urlPath) {
|
|
93
|
+
if (locationConfigParam.endsWith) {
|
|
94
|
+
res = $route.path.endsWith(locationConfigParam.urlPath);
|
|
95
|
+
} else if (!Object.keys(locationConfigParam).includes('exact') || locationConfigParam.exact) {
|
|
96
|
+
res = locationConfigParam.urlPath === $route.path;
|
|
97
|
+
} else {
|
|
98
|
+
res = $route.path.includes(locationConfigParam.urlPath);
|
|
99
|
+
}
|
|
84
100
|
} else if (locationConfigParam === params[param]) {
|
|
85
101
|
res = true;
|
|
86
102
|
} else {
|
package/core/types.ts
CHANGED
|
@@ -139,6 +139,10 @@ export type LocationConfig = {
|
|
|
139
139
|
cluster?: string[],
|
|
140
140
|
id?: string[],
|
|
141
141
|
mode?: string[],
|
|
142
|
+
/**
|
|
143
|
+
* path match from URL (excludes host address)
|
|
144
|
+
*/
|
|
145
|
+
path?: { [key: string]: string | boolean}[],
|
|
142
146
|
/**
|
|
143
147
|
* Query Params from URL
|
|
144
148
|
*/
|
|
@@ -31,8 +31,9 @@ export default {
|
|
|
31
31
|
|
|
32
32
|
data() {
|
|
33
33
|
return {
|
|
34
|
-
allFleet:
|
|
35
|
-
allBundles:
|
|
34
|
+
allFleet: [],
|
|
35
|
+
allBundles: [],
|
|
36
|
+
allBundleDeployments: [],
|
|
36
37
|
};
|
|
37
38
|
},
|
|
38
39
|
|
|
@@ -79,6 +80,12 @@ export default {
|
|
|
79
80
|
inStoreType: 'management',
|
|
80
81
|
type: FLEET.BUNDLE
|
|
81
82
|
},
|
|
83
|
+
|
|
84
|
+
allBundleDeployments: {
|
|
85
|
+
inStoreType: 'management',
|
|
86
|
+
type: FLEET.BUNDLE_DEPLOYMENT
|
|
87
|
+
},
|
|
88
|
+
|
|
82
89
|
allFleet: {
|
|
83
90
|
inStoreType: 'management',
|
|
84
91
|
type: FLEET.CLUSTER
|
|
@@ -89,6 +96,7 @@ export default {
|
|
|
89
96
|
}
|
|
90
97
|
}, this.$store);
|
|
91
98
|
|
|
99
|
+
this.allBundleDeployments = allDispatches.allBundleDeployments || [];
|
|
92
100
|
this.allBundles = allDispatches.allBundles || [];
|
|
93
101
|
this.allFleet = allDispatches.allFleet || [];
|
|
94
102
|
},
|
package/detail/pod.vue
CHANGED
|
@@ -14,6 +14,8 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
|
14
14
|
import day from 'dayjs';
|
|
15
15
|
import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
|
|
16
16
|
import { escapeHtml } from '@shell/utils/string';
|
|
17
|
+
import { NAMESPACE } from '@shell/config/types';
|
|
18
|
+
import { PROJECT } from '@shell/config/labels-annotations';
|
|
17
19
|
|
|
18
20
|
const POD_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/rancher-pod-containers-1/rancher-pod-containers?orgId=1';
|
|
19
21
|
const POD_METRICS_SUMMARY_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/rancher-pod-1/rancher-pod?orgId=1';
|
|
@@ -33,6 +35,18 @@ export default {
|
|
|
33
35
|
|
|
34
36
|
async fetch() {
|
|
35
37
|
this.showMetrics = await allDashboardsExist(this.$store, this.currentCluster.id, [POD_METRICS_DETAIL_URL, POD_METRICS_SUMMARY_URL]);
|
|
38
|
+
if (!this.showMetrics) {
|
|
39
|
+
const namespace = await this.$store.dispatch('cluster/find', { type: NAMESPACE, id: this.value.metadata.namespace });
|
|
40
|
+
|
|
41
|
+
const projectId = namespace?.metadata?.labels[PROJECT];
|
|
42
|
+
|
|
43
|
+
if (projectId) {
|
|
44
|
+
this.POD_PROJECT_METRICS_DETAIL_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-containers-1/rancher-pod-containers?orgId=1'`;
|
|
45
|
+
this.POD_PROJECT_METRICS_SUMMARY_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-1/rancher-pod?orgId=1`;
|
|
46
|
+
|
|
47
|
+
this.showProjectMetrics = await allDashboardsExist(this.$store, this.currentCluster.id, [this.POD_PROJECT_METRICS_DETAIL_URL, this.POD_PROJECT_METRICS_SUMMARY_URL], 'cluster', projectId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
36
50
|
},
|
|
37
51
|
|
|
38
52
|
data() {
|
|
@@ -45,10 +59,13 @@ export default {
|
|
|
45
59
|
return {
|
|
46
60
|
POD_METRICS_DETAIL_URL,
|
|
47
61
|
POD_METRICS_SUMMARY_URL,
|
|
62
|
+
POD_PROJECT_METRICS_DETAIL_URL: '',
|
|
63
|
+
POD_PROJECT_METRICS_SUMMARY_URL: '',
|
|
48
64
|
POD_OPTION,
|
|
49
|
-
showMetrics:
|
|
50
|
-
|
|
51
|
-
|
|
65
|
+
showMetrics: false,
|
|
66
|
+
showProjectMetrics: false,
|
|
67
|
+
selection: POD_OPTION,
|
|
68
|
+
metricsID: null,
|
|
52
69
|
};
|
|
53
70
|
},
|
|
54
71
|
|
|
@@ -278,6 +295,22 @@ export default {
|
|
|
278
295
|
/>
|
|
279
296
|
</template>
|
|
280
297
|
</Tab>
|
|
298
|
+
<Tab
|
|
299
|
+
v-if="showProjectMetrics"
|
|
300
|
+
:label="t('workload.container.titles.metrics')"
|
|
301
|
+
name="pod-metrics"
|
|
302
|
+
:weight="2.5"
|
|
303
|
+
>
|
|
304
|
+
<template #default="props">
|
|
305
|
+
<DashboardMetrics
|
|
306
|
+
v-if="props.active"
|
|
307
|
+
:detail-url="POD_PROJECT_METRICS_DETAIL_URL"
|
|
308
|
+
:summary-url="POD_PROJECT_METRICS_SUMMARY_URL"
|
|
309
|
+
:vars="graphVars"
|
|
310
|
+
graph-height="550px"
|
|
311
|
+
/>
|
|
312
|
+
</template>
|
|
313
|
+
</Tab>
|
|
281
314
|
</ResourceTabs>
|
|
282
315
|
</template>
|
|
283
316
|
<style scoped>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
3
3
|
import { NAMESPACE as NAMESPACE_COL } from '@shell/config/table-headers';
|
|
4
4
|
import {
|
|
5
|
-
POD, WORKLOAD_TYPES, SCALABLE_WORKLOAD_TYPES, SERVICE, INGRESS, NODE
|
|
5
|
+
POD, WORKLOAD_TYPES, SCALABLE_WORKLOAD_TYPES, SERVICE, INGRESS, NODE, NAMESPACE,
|
|
6
6
|
} from '@shell/config/types';
|
|
7
7
|
import ResourceTable from '@shell/components/ResourceTable';
|
|
8
8
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
@@ -16,6 +16,7 @@ import { mapGetters } from 'vuex';
|
|
|
16
16
|
import { allDashboardsExist } from '@shell/utils/grafana';
|
|
17
17
|
import PlusMinus from '@shell/components/form/PlusMinus';
|
|
18
18
|
import { matches } from '@shell/utils/selector';
|
|
19
|
+
import { PROJECT } from '@shell/config/labels-annotations';
|
|
19
20
|
|
|
20
21
|
const SCALABLE_TYPES = Object.values(SCALABLE_WORKLOAD_TYPES);
|
|
21
22
|
const WORKLOAD_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/rancher-workload-pods-1/rancher-workload-pods?orgId=1';
|
|
@@ -85,23 +86,37 @@ export default {
|
|
|
85
86
|
const isMetricsSupportedKind = METRICS_SUPPORTED_KINDS.includes(this.value.type);
|
|
86
87
|
|
|
87
88
|
this.showMetrics = isMetricsSupportedKind && await allDashboardsExist(this.$store, this.currentCluster.id, [WORKLOAD_METRICS_DETAIL_URL, WORKLOAD_METRICS_SUMMARY_URL]);
|
|
89
|
+
if (!this.showMetrics) {
|
|
90
|
+
const namespace = await this.$store.dispatch('cluster/find', { type: NAMESPACE, id: this.value.metadata.namespace });
|
|
88
91
|
|
|
92
|
+
const projectId = namespace?.metadata?.labels[PROJECT];
|
|
93
|
+
|
|
94
|
+
if (projectId) {
|
|
95
|
+
this.WORKLOAD_PROJECT_METRICS_DETAIL_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-containers-1/rancher-workload-pods?orgId=1'`;
|
|
96
|
+
this.WORKLOAD_PROJECT_METRICS_SUMMARY_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-1/rancher-workload?orgId=1`;
|
|
97
|
+
|
|
98
|
+
this.showProjectMetrics = await allDashboardsExist(this.$store, this.currentCluster.id, [this.WORKLOAD_PROJECT_METRICS_DETAIL_URL, this.WORKLOAD_PROJECT_METRICS_SUMMARY_URL], 'cluster', projectId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
89
101
|
this.findMatchingServices();
|
|
90
102
|
this.findMatchingIngresses();
|
|
91
103
|
},
|
|
92
104
|
|
|
93
105
|
data() {
|
|
94
106
|
return {
|
|
95
|
-
allPods:
|
|
96
|
-
allServices:
|
|
97
|
-
allIngresses:
|
|
98
|
-
matchingServices:
|
|
99
|
-
matchingIngresses:
|
|
100
|
-
allJobs:
|
|
101
|
-
allNodes:
|
|
107
|
+
allPods: [],
|
|
108
|
+
allServices: [],
|
|
109
|
+
allIngresses: [],
|
|
110
|
+
matchingServices: [],
|
|
111
|
+
matchingIngresses: [],
|
|
112
|
+
allJobs: [],
|
|
113
|
+
allNodes: [],
|
|
102
114
|
WORKLOAD_METRICS_DETAIL_URL,
|
|
103
115
|
WORKLOAD_METRICS_SUMMARY_URL,
|
|
104
|
-
|
|
116
|
+
POD_PROJECT_METRICS_DETAIL_URL: '',
|
|
117
|
+
POD_PROJECT_METRICS_SUMMARY_URL: '',
|
|
118
|
+
showMetrics: false,
|
|
119
|
+
showProjectMetrics: false,
|
|
105
120
|
};
|
|
106
121
|
},
|
|
107
122
|
|
|
@@ -421,6 +436,22 @@ export default {
|
|
|
421
436
|
/>
|
|
422
437
|
</template>
|
|
423
438
|
</Tab>
|
|
439
|
+
<Tab
|
|
440
|
+
v-if="showProjectMetrics"
|
|
441
|
+
:label="t('workload.container.titles.metrics')"
|
|
442
|
+
name="workload-metrics"
|
|
443
|
+
:weight="3"
|
|
444
|
+
>
|
|
445
|
+
<template #default="props">
|
|
446
|
+
<DashboardMetrics
|
|
447
|
+
v-if="props.active"
|
|
448
|
+
:detail-url="WORKLOAD_PROJECT_METRICS_DETAIL_URL"
|
|
449
|
+
:summary-url="WORKLOAD_PROJECT_METRICS_SUMMARY_URL"
|
|
450
|
+
:vars="graphVars"
|
|
451
|
+
graph-height="550px"
|
|
452
|
+
/>
|
|
453
|
+
</template>
|
|
454
|
+
</Tab>
|
|
424
455
|
<Tab
|
|
425
456
|
v-if="v1MonitoringUrl"
|
|
426
457
|
name="v1Metrics"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* eslint-disable jest/no-hooks */
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import Navlink from '@shell/edit/ui.cattle.io.navlink.vue';
|
|
4
|
+
import { _CREATE } from '@shell/config/query-params';
|
|
5
|
+
import CruResource from '@shell/components/CruResource';
|
|
6
|
+
|
|
7
|
+
describe('view: ui.cattle.io.navlink should', () => {
|
|
8
|
+
const name = 'test';
|
|
9
|
+
const url = 'http://test.com';
|
|
10
|
+
let wrapper: any;
|
|
11
|
+
|
|
12
|
+
const requiredSetup = () => ({
|
|
13
|
+
// Remove all these mocks after migration to Vue 2.7/3 due mixin logic
|
|
14
|
+
mocks: {
|
|
15
|
+
$store: {
|
|
16
|
+
getters: {
|
|
17
|
+
currentStore: () => 'current_store',
|
|
18
|
+
'current_store/schemaFor': jest.fn(),
|
|
19
|
+
'current_store/all': jest.fn(),
|
|
20
|
+
'i18n/t': (val) => val,
|
|
21
|
+
'i18n/exists': jest.fn(),
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
$route: { query: { AS: '' } },
|
|
25
|
+
$router: { applyQuery: jest.fn() },
|
|
26
|
+
},
|
|
27
|
+
propsData: {
|
|
28
|
+
metadata: { namespace: 'test' },
|
|
29
|
+
spec: { template: {} },
|
|
30
|
+
targetInfo: { mode: 'all' },
|
|
31
|
+
value: {},
|
|
32
|
+
mode: _CREATE,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
wrapper = mount(Navlink, { ...requiredSetup() });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
wrapper.destroy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('have "Create" button disabled before fields are filled in', () => {
|
|
46
|
+
const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
|
|
47
|
+
|
|
48
|
+
expect(saveButton.disabled).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('have "Create" button disabled when Link type is URL and only name is filled in', async() => {
|
|
51
|
+
const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
|
|
52
|
+
const nameField = wrapper.find('[data-testid="Navlink-name-field"]').find('input');
|
|
53
|
+
|
|
54
|
+
nameField.setValue(name);
|
|
55
|
+
|
|
56
|
+
await wrapper.vm.$nextTick();
|
|
57
|
+
|
|
58
|
+
expect(saveButton.disabled).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it('have "Create" button enabled when Link type is URL and all required fields are filled in', async() => {
|
|
61
|
+
const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
|
|
62
|
+
const nameField = wrapper.find('[data-testid="Navlink-name-field"]').find('input');
|
|
63
|
+
const urlField = wrapper.find('[data-testid="Navlink-url-field"]');
|
|
64
|
+
|
|
65
|
+
nameField.setValue(name);
|
|
66
|
+
urlField.setValue(url);
|
|
67
|
+
|
|
68
|
+
await wrapper.vm.$nextTick();
|
|
69
|
+
|
|
70
|
+
expect(saveButton.disabled).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('have "Create" button disabled when Link type is Service and and only name is filled in', async() => {
|
|
74
|
+
const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
|
|
75
|
+
const nameField = wrapper.find('[data-testid="Navlink-name-field"]').find('input');
|
|
76
|
+
const rg = wrapper.find('[data-testid="Navlink-link-radiogroup"]');
|
|
77
|
+
|
|
78
|
+
const serviceBttn = rg.findAll('.radio-label').at(1);
|
|
79
|
+
|
|
80
|
+
nameField.setValue(name);
|
|
81
|
+
serviceBttn.trigger('click');
|
|
82
|
+
await wrapper.vm.$nextTick();
|
|
83
|
+
|
|
84
|
+
expect(saveButton.disabled).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('have "Create" button enabled when Link type is Service and and all required fields are filled in', async() => {
|
|
88
|
+
const nameField = wrapper.find('[data-testid="Navlink-name-field"]').find('input');
|
|
89
|
+
const rg = wrapper.find('[data-testid="Navlink-link-radiogroup"]');
|
|
90
|
+
|
|
91
|
+
const serviceBttn = rg.findAll('.radio-label').at(1);
|
|
92
|
+
|
|
93
|
+
nameField.setValue(name);
|
|
94
|
+
serviceBttn.trigger('click');
|
|
95
|
+
await wrapper.vm.$nextTick();
|
|
96
|
+
|
|
97
|
+
const schemeField = wrapper.find('[data-testid="Navlink-scheme-field"]');
|
|
98
|
+
const serviceField = wrapper.find('[data-testid="Navlink-currentService-field"]');
|
|
99
|
+
|
|
100
|
+
schemeField.find('button').trigger('click');
|
|
101
|
+
await wrapper.trigger('keydown.down');
|
|
102
|
+
await wrapper.trigger('keydown.enter');
|
|
103
|
+
|
|
104
|
+
serviceField.find('button').trigger('click');
|
|
105
|
+
await wrapper.trigger('keydown.down');
|
|
106
|
+
await wrapper.trigger('keydown.enter');
|
|
107
|
+
|
|
108
|
+
expect(CruResource.computed.canSave()).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -11,6 +11,7 @@ import { set } from '@shell/utils/object';
|
|
|
11
11
|
import { FLEET } from '@shell/config/types';
|
|
12
12
|
import { convert, matching, simplify } from '@shell/utils/selector';
|
|
13
13
|
import throttle from 'lodash/throttle';
|
|
14
|
+
import { allHash } from '@shell/utils/promise';
|
|
14
15
|
|
|
15
16
|
export default {
|
|
16
17
|
name: 'CruClusterGroup',
|
|
@@ -27,10 +28,20 @@ export default {
|
|
|
27
28
|
mixins: [CreateEditView],
|
|
28
29
|
|
|
29
30
|
async fetch() {
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const _hash = {};
|
|
32
|
+
|
|
33
|
+
if (this.$store.getters['management/schemaFor'](FLEET.CLUSTER)) {
|
|
34
|
+
_hash.allClusters = await this.$store.dispatch('management/findAll', { type: FLEET.CLUSTER });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (this.$store.getters['management/schemaFor'](FLEET.WORKSPACE)) {
|
|
38
|
+
_hash.allWorkspaces = this.$store.dispatch('management/findAll', { type: FLEET.WORKSPACE });
|
|
32
39
|
}
|
|
33
|
-
|
|
40
|
+
|
|
41
|
+
const hash = await allHash(_hash);
|
|
42
|
+
|
|
43
|
+
this.allClusters = hash.allClusters || [];
|
|
44
|
+
this.allWorkspaces = hash.allWorkspaces || [];
|
|
34
45
|
|
|
35
46
|
if ( !this.value.spec?.selector ) {
|
|
36
47
|
this.value.spec = this.value.spec || {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import PersistentVolume from '@shell/edit/persistentvolume/index';
|
|
3
|
+
import { ExtendedVue, Vue } from 'vue/types/vue';
|
|
4
|
+
|
|
5
|
+
describe('view: PersistentVolume', () => {
|
|
6
|
+
it('should list enabled PV storage option if supported', () => {
|
|
7
|
+
const plugin = {
|
|
8
|
+
labelKey: 'persistentVolume.csi.label', supported: true, value: 'csi'
|
|
9
|
+
};
|
|
10
|
+
const resource = 'PersistentVolume';
|
|
11
|
+
const wrapper = mount(PersistentVolume as ExtendedVue<Vue, {}, {}, {}, PersistentVolume>, {
|
|
12
|
+
propsData: { value: { spec: { } } },
|
|
13
|
+
mocks: {
|
|
14
|
+
$store: {
|
|
15
|
+
dispatch: () => jest.fn(),
|
|
16
|
+
getters: {
|
|
17
|
+
'i18n/t': jest.fn(),
|
|
18
|
+
'i18n/exists': jest.fn(),
|
|
19
|
+
currentStore: () => 'cluster',
|
|
20
|
+
'features/get': () => jest.fn(),
|
|
21
|
+
'prefs/get': () => resource,
|
|
22
|
+
'cluster/schemaFor': () => {},
|
|
23
|
+
'cluster/all': () => [{}],
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
$fetchState: {
|
|
27
|
+
pending: false, error: true, timestamp: Date.now()
|
|
28
|
+
},
|
|
29
|
+
$route: {
|
|
30
|
+
params: { resource },
|
|
31
|
+
query: { AS: '' },
|
|
32
|
+
hash: '',
|
|
33
|
+
},
|
|
34
|
+
$router: {
|
|
35
|
+
currentRoute: {},
|
|
36
|
+
replace: jest.fn(),
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
stubs: { LabeledSelect: true }
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const select = wrapper.find('[data-testid="persistent-volume-plugin-select"]');
|
|
43
|
+
|
|
44
|
+
expect((select.vm as unknown as any).options).toStrictEqual(expect.arrayContaining([plugin]));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should select current plugin', () => {
|
|
48
|
+
const plugin = 'csi';
|
|
49
|
+
const resource = 'PersistentVolume';
|
|
50
|
+
const wrapper = mount(PersistentVolume as ExtendedVue<Vue, {}, {}, {}, PersistentVolume>, {
|
|
51
|
+
propsData: { value: { spec: { [plugin]: { value: plugin } } } },
|
|
52
|
+
mocks: {
|
|
53
|
+
$store: {
|
|
54
|
+
dispatch: () => jest.fn(),
|
|
55
|
+
getters: {
|
|
56
|
+
'i18n/t': jest.fn(),
|
|
57
|
+
'i18n/exists': jest.fn(),
|
|
58
|
+
currentStore: () => 'cluster',
|
|
59
|
+
'features/get': () => jest.fn(),
|
|
60
|
+
'prefs/get': () => resource,
|
|
61
|
+
'cluster/schemaFor': () => {},
|
|
62
|
+
'cluster/all': () => [{}],
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
$fetchState: {
|
|
66
|
+
pending: false, error: true, timestamp: Date.now()
|
|
67
|
+
},
|
|
68
|
+
$route: {
|
|
69
|
+
params: { resource },
|
|
70
|
+
query: { AS: '' },
|
|
71
|
+
hash: '',
|
|
72
|
+
},
|
|
73
|
+
$router: {
|
|
74
|
+
currentRoute: {},
|
|
75
|
+
replace: jest.fn(),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(wrapper.vm.plugin).toBe(plugin);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -13,9 +13,9 @@ import NodeAffinity from '@shell/components/form/NodeAffinity';
|
|
|
13
13
|
import { Checkbox } from '@components/Form/Checkbox';
|
|
14
14
|
import uniq from 'lodash/uniq';
|
|
15
15
|
import UnitInput from '@shell/components/form/UnitInput';
|
|
16
|
+
import { VOLUME_PLUGINS, LONGHORN_PLUGIN } from '@shell/config/persistentVolume';
|
|
16
17
|
import { NODE, PVC, STORAGE_CLASS } from '@shell/config/types';
|
|
17
18
|
import Loading from '@shell/components/Loading';
|
|
18
|
-
import { LONGHORN_PLUGIN, VOLUME_PLUGINS } from '@shell/models/persistentvolume';
|
|
19
19
|
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
|
20
20
|
import { clone } from '@shell/utils/object';
|
|
21
21
|
import InfoBox from '@shell/components/InfoBox';
|
|
@@ -258,6 +258,7 @@ export default {
|
|
|
258
258
|
:label="t('persistentVolume.plugin.label')"
|
|
259
259
|
:localized-label="true"
|
|
260
260
|
option-label="labelKey"
|
|
261
|
+
data-testid="persistent-volume-plugin-select"
|
|
261
262
|
:options="plugins"
|
|
262
263
|
:mode="modeOverride"
|
|
263
264
|
:required="true"
|
|
@@ -3,7 +3,7 @@ import KeyValue from '@shell/components/form/KeyValue';
|
|
|
3
3
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
4
4
|
import { RadioGroup } from '@components/Form/Radio';
|
|
5
5
|
import { _CREATE } from '@shell/config/query-params';
|
|
6
|
-
import { LONGHORN_DRIVER } from '@shell/
|
|
6
|
+
import { LONGHORN_DRIVER } from '@shell/config/types';
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
9
|
components: {
|
|
@@ -54,20 +54,20 @@ export default {
|
|
|
54
54
|
<div>
|
|
55
55
|
<div class="row mb-20">
|
|
56
56
|
<div class="col span-6">
|
|
57
|
-
<LabeledInput
|
|
58
|
-
v-model="value.spec.csi.fsType"
|
|
59
|
-
:mode="mode"
|
|
60
|
-
:label="t('persistentVolume.shared.filesystemType.label')"
|
|
61
|
-
:placeholder="t('persistentVolume.shared.filesystemType.placeholder')"
|
|
57
|
+
<LabeledInput
|
|
58
|
+
v-model="value.spec.csi.fsType"
|
|
59
|
+
:mode="mode"
|
|
60
|
+
:label="t('persistentVolume.shared.filesystemType.label')"
|
|
61
|
+
:placeholder="t('persistentVolume.shared.filesystemType.placeholder')"
|
|
62
62
|
/>
|
|
63
63
|
</div>
|
|
64
64
|
<div class="col span-6">
|
|
65
|
-
<LabeledInput
|
|
66
|
-
v-model="value.spec.csi.volumeHandle"
|
|
67
|
-
:mode="mode"
|
|
68
|
-
:label="t('persistentVolume.longhorn.volumeHandle.label')"
|
|
69
|
-
:placeholder="t('persistentVolume.longhorn.volumeHandle.placeholder')"
|
|
70
|
-
:required="true"
|
|
65
|
+
<LabeledInput
|
|
66
|
+
v-model="value.spec.csi.volumeHandle"
|
|
67
|
+
:mode="mode"
|
|
68
|
+
:label="t('persistentVolume.longhorn.volumeHandle.label')"
|
|
69
|
+
:placeholder="t('persistentVolume.longhorn.volumeHandle.placeholder')"
|
|
70
|
+
:required="true"
|
|
71
71
|
/>
|
|
72
72
|
</div>
|
|
73
73
|
</div>
|
|
@@ -43,14 +43,28 @@ export default {
|
|
|
43
43
|
const configMap = this.value.spec.rkeConfig?.registries?.configs || {};
|
|
44
44
|
const entries = [];
|
|
45
45
|
|
|
46
|
+
const defaultAddValue = {
|
|
47
|
+
hostname: '',
|
|
48
|
+
authConfigSecretName: null,
|
|
49
|
+
caBundle: '',
|
|
50
|
+
insecureSkipVerify: false,
|
|
51
|
+
tlsSecretName: null,
|
|
52
|
+
};
|
|
53
|
+
|
|
46
54
|
for ( const hostname in configMap ) {
|
|
55
|
+
if (configMap[hostname]) {
|
|
56
|
+
configMap[hostname].insecureSkipVerify = configMap[hostname].insecureSkipVerify ?? defaultAddValue.insecureSkipVerify;
|
|
57
|
+
configMap[hostname].authConfigSecretName = configMap[hostname].authConfigSecretName ?? defaultAddValue.authConfigSecretName;
|
|
58
|
+
configMap[hostname].caBundle = configMap[hostname].caBundle ?? defaultAddValue.caBundle;
|
|
59
|
+
configMap[hostname].tlsSecretName = configMap[hostname].tlsSecretName ?? defaultAddValue.tlsSecretName;
|
|
60
|
+
}
|
|
47
61
|
entries.push({
|
|
48
62
|
hostname,
|
|
49
63
|
...configMap[hostname],
|
|
50
64
|
});
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
return { entries };
|
|
67
|
+
return { entries, defaultAddValue };
|
|
54
68
|
},
|
|
55
69
|
|
|
56
70
|
computed: {
|
|
@@ -59,16 +73,6 @@ export default {
|
|
|
59
73
|
return TYPES.TLS;
|
|
60
74
|
},
|
|
61
75
|
},
|
|
62
|
-
|
|
63
|
-
defaultAddValue() {
|
|
64
|
-
return {
|
|
65
|
-
hostname: '',
|
|
66
|
-
authConfigSecretName: null,
|
|
67
|
-
caBundle: '',
|
|
68
|
-
insecureSkipVerify: false,
|
|
69
|
-
tlsSecretName: null,
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
76
|
},
|
|
73
77
|
|
|
74
78
|
mounted() {
|
|
@@ -17,7 +17,7 @@ import { CATALOG } from '@shell/config/labels-annotations';
|
|
|
17
17
|
import { CAPI, MANAGEMENT, DEFAULT_WORKSPACE } from '@shell/config/types';
|
|
18
18
|
import { mapFeature, RKE2 as RKE2_FEATURE } from '@shell/store/features';
|
|
19
19
|
import { allHash } from '@shell/utils/promise';
|
|
20
|
-
import { BLANK_CLUSTER } from '@shell/store';
|
|
20
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
21
21
|
import { ELEMENTAL_PRODUCT_NAME, ELEMENTAL_CLUSTER_PROVIDER } from '../../config/elemental-types';
|
|
22
22
|
import Rke2Config from './rke2';
|
|
23
23
|
import Import from './import';
|
|
@@ -1645,7 +1645,11 @@ export default {
|
|
|
1645
1645
|
const harvesterKubeconfigSecret = await this.createKubeconfigSecret(kubeconfig);
|
|
1646
1646
|
|
|
1647
1647
|
set(this.agentConfig, 'cloud-provider-config', `secret://fleet-default:${ harvesterKubeconfigSecret?.metadata?.name }`);
|
|
1648
|
-
|
|
1648
|
+
|
|
1649
|
+
if (this.isCreate) {
|
|
1650
|
+
set(this.chartValues, `${ HARVESTER_CLOUD_PROVIDER }.global.cattle.clusterName`, this.value.metadata.name);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1649
1653
|
set(this.chartValues, `${ HARVESTER_CLOUD_PROVIDER }.cloudConfigPath`, '/var/lib/rancher/rke2/etc/config-files/cloud-provider-config');
|
|
1650
1654
|
}
|
|
1651
1655
|
} catch (err) {
|
|
@@ -11,8 +11,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
|
11
11
|
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
|
12
12
|
import { PROVISIONER_OPTIONS } from '@shell/models/storage.k8s.io.storageclass';
|
|
13
13
|
import { mapFeature, UNSUPPORTED_STORAGE_DRIVERS } from '@shell/store/features';
|
|
14
|
-
import { CSI_DRIVER } from '@shell/config/types';
|
|
15
|
-
import { LONGHORN_DRIVER } from '@shell/models/persistentvolume';
|
|
14
|
+
import { CSI_DRIVER, LONGHORN_DRIVER } from '@shell/config/types';
|
|
16
15
|
|
|
17
16
|
export default {
|
|
18
17
|
name: 'StorageClass',
|