@rancher/shell 0.3.23 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/base/_variables.scss +1 -0
- package/assets/styles/themes/_dark.scss +1 -0
- package/assets/styles/themes/_light.scss +6 -5
- package/assets/translations/en-us.yaml +44 -17
- package/assets/translations/zh-hans.yaml +2 -2
- package/components/ClusterIconMenu.vue +143 -0
- package/components/CruResource.vue +7 -1
- package/components/ExplorerProjectsNamespaces.vue +11 -1
- package/components/FixedBanner.vue +17 -1
- package/components/Loading.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/Questions/__tests__/Yaml.test.ts +3 -2
- package/components/SideNav.vue +1 -1
- package/components/SortableTable/index.vue +3 -2
- package/components/auth/RoleDetailEdit.vue +15 -2
- package/components/auth/login/saml.vue +12 -1
- package/components/form/LabeledSelect.vue +12 -5
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/Members/MembershipEditor.vue +6 -1
- package/components/form/SelectOrCreateAuthSecret.vue +7 -0
- package/components/form/__tests__/KeyValue.test.ts +6 -3
- package/components/form/__tests__/LabeledSelect.test.ts +18 -0
- package/components/formatter/PodsUsage.vue +11 -36
- package/components/formatter/PrincipalGroupBindings.vue +8 -5
- package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
- package/components/nav/Group.vue +62 -34
- package/components/nav/Header.vue +13 -6
- package/components/nav/Pinned.vue +47 -0
- package/components/nav/TopLevelMenu.vue +673 -325
- package/components/nav/Type.vue +88 -8
- package/config/home-links.js +1 -1
- package/config/product/istio.js +15 -5
- package/config/router.js +3 -9
- package/config/table-headers.js +5 -6
- package/config/uiplugins.js +1 -0
- package/core/plugin-helpers.js +3 -0
- package/core/types.ts +6 -1
- package/creators/app/files/.vscode/settings.json +0 -1
- package/creators/pkg/init +2 -2
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
- package/detail/provisioning.cattle.io.cluster.vue +7 -5
- package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
- package/edit/__tests__/namespace.test.ts +5 -3
- package/edit/fleet.cattle.io.gitrepo.vue +43 -15
- package/edit/logging.banzaicloud.io.output/index.vue +7 -0
- package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
- package/edit/namespace.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +9 -8
- package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
- package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
- package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
- package/edit/provisioning.cattle.io.cluster/rke2.vue +253 -582
- package/edit/workload/storage/ContainerMountPaths.vue +7 -5
- package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
- package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
- package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
- package/initialize/App.js +2 -0
- package/initialize/client.js +63 -51
- package/initialize/index.js +7 -5
- package/layouts/default.vue +10 -2
- package/layouts/home.vue +6 -2
- package/layouts/plain.vue +9 -2
- package/list/fleet.cattle.io.cluster.vue +2 -2
- package/list/management.cattle.io.feature.vue +1 -1
- package/machine-config/amazonec2.vue +1 -0
- package/machine-config/vmwarevsphere.vue +48 -7
- package/mixins/brand.js +0 -8
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +3 -3
- package/mixins/fetch.client.js +3 -3
- package/models/__tests__/management.cattle.io.node.ts +96 -0
- package/models/__tests__/node.ts +74 -0
- package/models/cluster/node.js +6 -5
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
- package/models/management.cattle.io.cluster.js +22 -1
- package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
- package/models/management.cattle.io.globalrole.js +17 -2
- package/models/management.cattle.io.node.js +6 -4
- package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
- package/models/management.cattle.io.roletemplate.js +17 -2
- package/package.json +2 -6
- package/pages/__tests__/prefs.test.ts +1 -1
- package/pages/about.vue +2 -0
- package/pages/auth/setup.vue +5 -4
- package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
- package/pages/c/_cluster/monitoring/index.vue +8 -3
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
- package/pages/c/_cluster/uiplugins/index.vue +64 -64
- package/pages/diagnostic.vue +0 -39
- package/pages/home.vue +1 -1
- package/pages/prefs.vue +3 -13
- package/plugins/dashboard-store/normalize.js +4 -4
- package/plugins/dashboard-store/resource-class.js +1 -1
- package/plugins/int-number.js +5 -2
- package/plugins/positive-int-number.js +19 -0
- package/plugins/steve/__tests__/getters.spec.ts +15 -0
- package/plugins/steve/getters.js +22 -10
- package/public/index.html +4 -2
- package/rancher-components/BadgeState/BadgeState.vue +5 -1
- package/rancher-components/Banner/Banner.test.ts +51 -1
- package/rancher-components/Banner/Banner.vue +134 -53
- package/rancher-components/Card/Card.test.ts +37 -0
- package/rancher-components/Card/Card.vue +24 -7
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
- package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
- package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
- package/rancher-components/Form/Radio/RadioButton.vue +30 -13
- package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
- package/rancher-components/StringList/StringList.test.ts +453 -49
- package/rancher-components/StringList/StringList.vue +92 -58
- package/scripts/extension/parse-tag-name +0 -0
- package/store/index.js +4 -0
- package/store/prefs.js +4 -4
- package/store/type-map.js +2 -16
- package/types/shell/index.d.ts +26 -14
- package/utils/__tests__/cluster.test.ts +55 -0
- package/utils/__tests__/object.test.ts +21 -2
- package/utils/__tests__/sort.test.ts +61 -0
- package/utils/cluster.js +47 -1
- package/utils/object.js +12 -5
- package/utils/string.js +12 -0
- package/utils/validators/formRules/__tests__/index.test.ts +13 -1
- package/utils/validators/formRules/index.ts +4 -0
- package/utils/validators/role-template.js +9 -1
- package/utils/version.js +1 -1
- package/vue.config.js +1 -4
- package/yarn-error.log +200 -0
- package/content/docs/en-us/getting-started.md +0 -224
- package/content/docs/en-us/whats-new.md +0 -29
- package/content/docs/zh-hans/getting-started.md +0 -224
- package/content/docs/zh-hans/whats-new.md +0 -28
- package/pages/docs/_doc.vue +0 -345
- package/pages/docs/toc.js +0 -27
- package/plugins/console.js +0 -34
package/components/nav/Type.vue
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import Favorite from '@shell/components/nav/Favorite';
|
|
3
3
|
import { FAVORITE, USED } from '@shell/store/type-map';
|
|
4
|
+
import { linkActiveClass } from '@shell/config/router';
|
|
4
5
|
|
|
5
6
|
const showFavoritesFor = [FAVORITE, USED];
|
|
6
7
|
|
|
@@ -27,12 +28,65 @@ export default {
|
|
|
27
28
|
|
|
28
29
|
data() {
|
|
29
30
|
return {
|
|
30
|
-
near:
|
|
31
|
-
over:
|
|
31
|
+
near: false,
|
|
32
|
+
over: false,
|
|
33
|
+
menuPath: this.type.route ? this.$router.resolve(this.type.route)?.route?.path : undefined,
|
|
34
|
+
linkActiveClass
|
|
32
35
|
};
|
|
33
36
|
},
|
|
34
37
|
|
|
35
38
|
computed: {
|
|
39
|
+
isCurrent() {
|
|
40
|
+
// This is required to avoid scenarios where fragments break vue routers location matching
|
|
41
|
+
// For example, the following fails
|
|
42
|
+
// Curruent Path /c/c-m-hzqf4tqt/explorer/members#project-membership
|
|
43
|
+
// Menu Path /c/c-m-hzqf4tqt/explorer/members
|
|
44
|
+
// vue-router exact-path="true" fixes this (https://v3.router.vuejs.org/api/#exact-path),
|
|
45
|
+
// but fails when the the current path is a child (for instance a resource detail page)
|
|
46
|
+
|
|
47
|
+
// Scenarios to consider
|
|
48
|
+
// - Fragement world
|
|
49
|
+
// Curruent Path /c/c-m-hzqf4tqt/explorer/members#project-membership
|
|
50
|
+
// Menu Path /c/c-m-hzqf4tqt/explorer/members
|
|
51
|
+
// - Similar current paths
|
|
52
|
+
// /c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundlenamespacemapping
|
|
53
|
+
// /c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundle
|
|
54
|
+
// - Other menu items that appear in current menu item
|
|
55
|
+
// /c/c-m-hzqf4tqt/fleet
|
|
56
|
+
// /c/c-m-hzqf4tqt/fleet/management.cattle.io.fleetworkspace
|
|
57
|
+
|
|
58
|
+
// If there's no hash the n-link will determine it's linkActiveClass correctly, so avoid this faff
|
|
59
|
+
const invalidHash = !this.$route.hash;
|
|
60
|
+
// Lets be super safe
|
|
61
|
+
const invalidProps = !this.menuPath || !this.$route.path;
|
|
62
|
+
|
|
63
|
+
if (invalidHash || invalidProps) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// We're kind of, but in a fixing way, copying n-link --> vue-router link see vue-router/src/components/link.js & vue-router/src/util/route.js
|
|
68
|
+
// We're only going to compare the path and ignore query and fragment
|
|
69
|
+
|
|
70
|
+
if (this.type.exact) {
|
|
71
|
+
return this.$route.path === this.menuPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const currentPath = this.$route.path.split('/');
|
|
75
|
+
const menuPath = this.menuPath.split('/');
|
|
76
|
+
|
|
77
|
+
if (menuPath.length > currentPath.length) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < menuPath.length; i++) {
|
|
82
|
+
if (menuPath[i] !== currentPath[i]) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
},
|
|
89
|
+
|
|
36
90
|
showFavorite() {
|
|
37
91
|
return ( this.type.mode && this.near && showFavoritesFor.includes(this.type.mode) );
|
|
38
92
|
},
|
|
@@ -40,6 +94,10 @@ export default {
|
|
|
40
94
|
showCount() {
|
|
41
95
|
return typeof this.type.count !== 'undefined';
|
|
42
96
|
},
|
|
97
|
+
|
|
98
|
+
namespaceIcon() {
|
|
99
|
+
return this.type.namespaced;
|
|
100
|
+
},
|
|
43
101
|
},
|
|
44
102
|
|
|
45
103
|
methods: {
|
|
@@ -76,7 +134,7 @@ export default {
|
|
|
76
134
|
:to="type.route"
|
|
77
135
|
tag="li"
|
|
78
136
|
class="child nav-type"
|
|
79
|
-
:class="{'root': isRoot, [`depth-${depth}`]: true}"
|
|
137
|
+
:class="{'root': isRoot, [`depth-${depth}`]: true, [linkActiveClass]: isCurrent}"
|
|
80
138
|
:exact="type.exact"
|
|
81
139
|
>
|
|
82
140
|
<a
|
|
@@ -102,6 +160,10 @@ export default {
|
|
|
102
160
|
v-if="showFavorite"
|
|
103
161
|
:resource="type.name"
|
|
104
162
|
/>
|
|
163
|
+
<i
|
|
164
|
+
v-if="namespaceIcon"
|
|
165
|
+
class="icon icon-namespace namespaced"
|
|
166
|
+
/>
|
|
105
167
|
{{ type.count }}
|
|
106
168
|
</span>
|
|
107
169
|
</a>
|
|
@@ -127,13 +189,16 @@ export default {
|
|
|
127
189
|
</template>
|
|
128
190
|
|
|
129
191
|
<style lang="scss" scoped>
|
|
192
|
+
.namespaced {
|
|
193
|
+
margin-right: 4px;
|
|
194
|
+
}
|
|
195
|
+
|
|
130
196
|
.child {
|
|
131
197
|
margin: 0 var(--outline) 0 0;
|
|
132
198
|
|
|
133
199
|
.label {
|
|
134
200
|
align-items: center;
|
|
135
201
|
grid-area: label;
|
|
136
|
-
display: flex;
|
|
137
202
|
overflow: hidden;
|
|
138
203
|
text-overflow: ellipsis;
|
|
139
204
|
|
|
@@ -166,6 +231,7 @@ export default {
|
|
|
166
231
|
text-overflow: ellipsis;
|
|
167
232
|
white-space: nowrap;
|
|
168
233
|
color: var(--body-text);
|
|
234
|
+
height: 33px;
|
|
169
235
|
|
|
170
236
|
&:hover {
|
|
171
237
|
background: var(--nav-hover);
|
|
@@ -181,26 +247,40 @@ export default {
|
|
|
181
247
|
grid-area: favorite;
|
|
182
248
|
font-size: 12px;
|
|
183
249
|
position: relative;
|
|
250
|
+
vertical-align: middle;
|
|
251
|
+
margin-right: 4px;
|
|
184
252
|
}
|
|
185
253
|
|
|
186
254
|
.count {
|
|
187
|
-
grid-area: count;
|
|
188
255
|
font-size: 12px;
|
|
189
|
-
text-align: right;
|
|
190
256
|
justify-items: center;
|
|
191
257
|
padding-right: 4px;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
192
260
|
}
|
|
193
261
|
|
|
194
262
|
&.nav-type:not(.depth-0) {
|
|
195
263
|
A {
|
|
196
|
-
|
|
197
|
-
padding: 5.5px 7px 5.5px 10px;
|
|
264
|
+
padding-left: 16px;
|
|
198
265
|
}
|
|
199
266
|
|
|
200
267
|
::v-deep .label I {
|
|
201
268
|
padding-right: 2px;
|
|
202
269
|
}
|
|
203
270
|
}
|
|
271
|
+
|
|
272
|
+
&.nav-type:is(.depth-1) {
|
|
273
|
+
A {
|
|
274
|
+
font-size: 13px;
|
|
275
|
+
padding-left: 23px;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
&.nav-type:not(.depth-0):not(.depth-1) {
|
|
280
|
+
A {
|
|
281
|
+
padding-left: 14px;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
204
284
|
}
|
|
205
285
|
|
|
206
286
|
</style>
|
package/config/home-links.js
CHANGED
package/config/product/istio.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGE, NAME as NAME_HEADER, STATE } from '@shell/config/table-headers';
|
|
1
|
+
import { AGE, NAME as NAME_HEADER, NAMESPACE as NAMESPACE_HEADER, STATE } from '@shell/config/table-headers';
|
|
2
2
|
import { ISTIO } from '@shell/config/types';
|
|
3
3
|
import { DSL, IF_HAVE } from '@shell/store/type-map';
|
|
4
4
|
|
|
@@ -14,9 +14,10 @@ export function init(store) {
|
|
|
14
14
|
} = DSL(store, NAME);
|
|
15
15
|
|
|
16
16
|
product({
|
|
17
|
-
ifHaveGroup:
|
|
18
|
-
ifHave:
|
|
19
|
-
icon:
|
|
17
|
+
ifHaveGroup: /^(.*\.)*istio\.io$/,
|
|
18
|
+
ifHave: IF_HAVE.NOT_V1_ISTIO,
|
|
19
|
+
icon: 'istio',
|
|
20
|
+
showNamespaceFilter: true,
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
virtualType({
|
|
@@ -42,7 +43,9 @@ export function init(store) {
|
|
|
42
43
|
'networking.istio.io.envoyfilter',
|
|
43
44
|
'networking.istio.io.serviceentry',
|
|
44
45
|
'networking.istio.io.sidecar',
|
|
45
|
-
'networking.istio.io.
|
|
46
|
+
'networking.istio.io.proxyconfig',
|
|
47
|
+
'networking.istio.io.workloadentry',
|
|
48
|
+
'networking.istio.io.workloadgroup',
|
|
46
49
|
], 'Networking');
|
|
47
50
|
|
|
48
51
|
basicType([
|
|
@@ -58,9 +61,16 @@ export function init(store) {
|
|
|
58
61
|
'security.istio.io.requestauthentication',
|
|
59
62
|
], 'Security');
|
|
60
63
|
|
|
64
|
+
basicType([
|
|
65
|
+
'install.istio.io.istiooperator',
|
|
66
|
+
'telemetry.istio.io.telemetry',
|
|
67
|
+
'extensions.istio.io.wasmplugin',
|
|
68
|
+
], 'Advanced');
|
|
69
|
+
|
|
61
70
|
headers(ISTIO.VIRTUAL_SERVICE, [
|
|
62
71
|
STATE,
|
|
63
72
|
NAME_HEADER,
|
|
73
|
+
NAMESPACE_HEADER,
|
|
64
74
|
{
|
|
65
75
|
name: 'gateways',
|
|
66
76
|
label: 'Gateways',
|
package/config/router.js
CHANGED
|
@@ -6,13 +6,15 @@ import scrollBehavior from '../utils/router.scrollBehavior.js';
|
|
|
6
6
|
|
|
7
7
|
const emptyFn = () => {};
|
|
8
8
|
|
|
9
|
+
export const linkActiveClass = 'nuxt-link-active';
|
|
10
|
+
|
|
9
11
|
Vue.use(Router);
|
|
10
12
|
|
|
11
13
|
export const routerOptions = {
|
|
12
14
|
mode: 'history',
|
|
13
15
|
// Note: router base comes from the ROUTER_BASE env var
|
|
14
16
|
base: process.env.routerBase || '/',
|
|
15
|
-
linkActiveClass
|
|
17
|
+
linkActiveClass,
|
|
16
18
|
linkExactActiveClass: 'nuxt-link-exact-active',
|
|
17
19
|
scrollBehavior,
|
|
18
20
|
|
|
@@ -72,10 +74,6 @@ export const routerOptions = {
|
|
|
72
74
|
path: '/auth/verify',
|
|
73
75
|
component: () => interopDefault(import('../pages/auth/verify.vue' /* webpackChunkName: "pages/auth/verify" */)),
|
|
74
76
|
name: 'auth-verify'
|
|
75
|
-
}, {
|
|
76
|
-
path: '/docs/toc',
|
|
77
|
-
component: () => interopDefault(import('../pages/docs/toc.js' /* webpackChunkName: "pages/docs/toc" */)),
|
|
78
|
-
name: 'docs-toc'
|
|
79
77
|
}, {
|
|
80
78
|
path: '/rio/mesh',
|
|
81
79
|
component: () => interopDefault(import('../pages/rio/mesh.vue' /* webpackChunkName: "pages/rio/mesh" */)),
|
|
@@ -84,10 +82,6 @@ export const routerOptions = {
|
|
|
84
82
|
path: '/c/:cluster',
|
|
85
83
|
component: () => interopDefault(import('../pages/c/_cluster/index.vue' /* webpackChunkName: "pages/c/_cluster/index" */)),
|
|
86
84
|
name: 'c-cluster'
|
|
87
|
-
}, {
|
|
88
|
-
path: '/docs/:doc?',
|
|
89
|
-
component: () => interopDefault(import('../pages/docs/_doc.vue' /* webpackChunkName: "pages/docs/_doc" */)),
|
|
90
|
-
name: 'docs-doc'
|
|
91
85
|
}, {
|
|
92
86
|
path: '/c/:cluster/apps',
|
|
93
87
|
component: () => interopDefault(import('../pages/c/_cluster/apps/index.vue' /* webpackChunkName: "pages/c/_cluster/apps/index" */)),
|
package/config/table-headers.js
CHANGED
|
@@ -993,13 +993,12 @@ export const UI_PLUGIN_CATALOG = [
|
|
|
993
993
|
name: 'image',
|
|
994
994
|
sort: ['image'],
|
|
995
995
|
labelKey: 'plugins.manageCatalog.headers.image.label',
|
|
996
|
-
value: '
|
|
996
|
+
value: 'image'
|
|
997
997
|
},
|
|
998
998
|
{
|
|
999
|
-
name:
|
|
1000
|
-
sort:
|
|
1001
|
-
labelKey:
|
|
1002
|
-
value:
|
|
1003
|
-
formatter: 'ExtensionCache'
|
|
999
|
+
name: 'repository',
|
|
1000
|
+
sort: ['repository'],
|
|
1001
|
+
labelKey: 'plugins.manageCatalog.headers.repository.label',
|
|
1002
|
+
value: 'repo.metadata.name'
|
|
1004
1003
|
}
|
|
1005
1004
|
];
|
package/config/uiplugins.js
CHANGED
|
@@ -49,6 +49,7 @@ export const UI_PLUGIN_CHART_ANNOTATIONS = {
|
|
|
49
49
|
UI_VERSION: 'catalog.cattle.io/ui-version',
|
|
50
50
|
EXTENSIONS_HOST: 'catalog.cattle.io/ui-extensions-host',
|
|
51
51
|
DISPLAY_NAME: 'catalog.cattle.io/display-name',
|
|
52
|
+
HIDDEN_BUILTIN: 'catalog.cattle.io/ui-hidden-builtin',
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
// Extension catalog labels
|
package/core/plugin-helpers.js
CHANGED
|
@@ -85,6 +85,9 @@ function checkExtensionRouteBinding($route, locationConfig, context) {
|
|
|
85
85
|
// also handle "mode" in a separate way because it mainly depends on query params
|
|
86
86
|
} else if (param === 'mode') {
|
|
87
87
|
res = checkRouteMode($route, locationConfigParam);
|
|
88
|
+
} else if (param === 'resource') {
|
|
89
|
+
// Match exact resource but also allow resource of '*' to match any resource
|
|
90
|
+
res = (params[param] && locationConfigParam === '*') || locationConfigParam === params[param];
|
|
88
91
|
} else if (param === 'context') {
|
|
89
92
|
// Need all keys and values to match
|
|
90
93
|
res = isEqual(locationConfigParam, context);
|
package/core/types.ts
CHANGED
|
@@ -102,7 +102,7 @@ export type Action = {
|
|
|
102
102
|
icon?: string;
|
|
103
103
|
multiple?: boolean;
|
|
104
104
|
enabled?: Function | boolean;
|
|
105
|
-
invoke: (opts: ActionOpts, resources: any[]) => void | boolean | Promise<boolean>;
|
|
105
|
+
invoke: (opts: ActionOpts, resources: any[], globals?: any) => void | boolean | Promise<boolean>;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
108
|
/** Definition of a panel (options that can be passed when defining an extension panel enhancement) */
|
|
@@ -567,3 +567,8 @@ export interface IPlugin {
|
|
|
567
567
|
*/
|
|
568
568
|
DSL(store: any, productName: string): DSLReturnType;
|
|
569
569
|
}
|
|
570
|
+
|
|
571
|
+
// Internal interface
|
|
572
|
+
// Built-in extensions may use this, but external extensions should not, as this is subject to change
|
|
573
|
+
// Defined as any for now
|
|
574
|
+
export type IInternal = any;
|
package/creators/pkg/init
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import HorizontalPodAutoScaler from '@shell/detail/autoscaling.horizontalpodautoscaler/index.vue';
|
|
3
|
+
|
|
4
|
+
describe('view: autoscaling.horizontalpodautoscaler', () => {
|
|
5
|
+
const mockStore = {
|
|
6
|
+
getters: {
|
|
7
|
+
'i18n/t': (text: string) => text,
|
|
8
|
+
t: (text: string) => text,
|
|
9
|
+
currentStore: () => 'current_store',
|
|
10
|
+
'current_store/schemaFor': jest.fn(),
|
|
11
|
+
'current_store/all': jest.fn(),
|
|
12
|
+
workspace: jest.fn(),
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mocks = {
|
|
17
|
+
$store: mockStore,
|
|
18
|
+
$fetchState: { pending: false },
|
|
19
|
+
$route: {
|
|
20
|
+
query: { AS: '' },
|
|
21
|
+
name: {
|
|
22
|
+
endsWith: () => {
|
|
23
|
+
return false;
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const value = {
|
|
30
|
+
status: {
|
|
31
|
+
currentMetrics: [
|
|
32
|
+
{
|
|
33
|
+
resource: {
|
|
34
|
+
current: {
|
|
35
|
+
averageUtilization: 8,
|
|
36
|
+
averageValue: '11481088'
|
|
37
|
+
},
|
|
38
|
+
name: 'memory'
|
|
39
|
+
},
|
|
40
|
+
type: 'Resource'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
resource: {
|
|
44
|
+
current: {
|
|
45
|
+
averageUtilization: 0,
|
|
46
|
+
averageValue: '1m'
|
|
47
|
+
},
|
|
48
|
+
name: 'cpu'
|
|
49
|
+
},
|
|
50
|
+
type: 'Resource'
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
spec: {
|
|
55
|
+
maxReplicas: 10,
|
|
56
|
+
metrics: [
|
|
57
|
+
{
|
|
58
|
+
resource: {
|
|
59
|
+
name: 'memory',
|
|
60
|
+
target: {
|
|
61
|
+
averageUtilization: 80,
|
|
62
|
+
type: 'Utilization'
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
type: 'Resource'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
resource: {
|
|
69
|
+
name: 'cpu',
|
|
70
|
+
target: {
|
|
71
|
+
averageUtilization: 50,
|
|
72
|
+
type: 'Utilization'
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
type: 'Resource'
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
minReplicas: 1,
|
|
79
|
+
scaleTargetRef: {
|
|
80
|
+
apiVersion: 'apps/v1',
|
|
81
|
+
kind: 'Deployment',
|
|
82
|
+
name: 'php-apache'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const metricsValue = Object.values(value.spec.metrics);
|
|
88
|
+
const currentMetrics = Object.values(value.status.currentMetrics);
|
|
89
|
+
|
|
90
|
+
const wrapper = shallowMount(HorizontalPodAutoScaler, {
|
|
91
|
+
mocks,
|
|
92
|
+
propsData: { value },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe.each(value.spec.metrics)('should display metrics for each resource:', (metric) => {
|
|
96
|
+
const name = metric.resource.name;
|
|
97
|
+
|
|
98
|
+
it(`${ name }:`, () => {
|
|
99
|
+
// Resource metrics
|
|
100
|
+
const resourceValue = wrapper.find(`[data-testid="resource-metrics-value-${ name }"]`);
|
|
101
|
+
const resourceName = wrapper.find(`[data-testid="resource-metrics-name-${ name }"]`);
|
|
102
|
+
const metricValue = metricsValue.find((f) => f.resource.name === name)?.resource;
|
|
103
|
+
|
|
104
|
+
// Current Metrics
|
|
105
|
+
const averageUtilization = wrapper.find(`[data-testid="current-metrics-Average Utilization-${ name }"]`);
|
|
106
|
+
const averageValue = wrapper.find(`[data-testid="current-metrics-Average Value-${ name }"]`);
|
|
107
|
+
const currentResource = currentMetrics.find((f) => f.resource.name === name)?.resource.current;
|
|
108
|
+
|
|
109
|
+
// Resource metrics
|
|
110
|
+
expect(resourceValue.element.textContent).toBe(`${ metricValue?.target?.averageUtilization }`);
|
|
111
|
+
expect(resourceName.element.textContent).toBe(`${ metricValue?.name }`);
|
|
112
|
+
|
|
113
|
+
// Current Metrics
|
|
114
|
+
expect(averageUtilization.element.textContent).toBe(`${ currentResource?.averageUtilization }`);
|
|
115
|
+
expect(averageValue.element.textContent).toBe(`${ currentResource?.averageValue }`);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -43,7 +43,7 @@ export default {
|
|
|
43
43
|
return metrics.map((metric) => {
|
|
44
44
|
const metricValue = get(metric, camelCase(metric.type));
|
|
45
45
|
const targetType = metricValue?.target?.type;
|
|
46
|
-
const currentMatch = findBy(currentMetrics, '
|
|
46
|
+
const currentMatch = findBy(currentMetrics, 'resource.name', metric.resource.name);
|
|
47
47
|
const current = currentMatch ? get(currentMatch, `${ camelCase(metric.type) }.current`) : null;
|
|
48
48
|
const currentMetricsKVs = [];
|
|
49
49
|
|
|
@@ -128,7 +128,7 @@ export default {
|
|
|
128
128
|
<label class="text-label">
|
|
129
129
|
<t k="hpa.metrics.headers.value" />:
|
|
130
130
|
</label>
|
|
131
|
-
<span>{{ metric.targetValue }}</span>
|
|
131
|
+
<span :data-testid="`resource-metrics-value-${metric.subRowContent.resourceName}`">{{ metric.targetValue }}</span>
|
|
132
132
|
</div>
|
|
133
133
|
<div v-if="metric.metricSource === 'Object'">
|
|
134
134
|
<div class="mb-5">
|
|
@@ -155,7 +155,7 @@ export default {
|
|
|
155
155
|
<label class="text-label">
|
|
156
156
|
<t k="hpa.metrics.headers.resource" />:
|
|
157
157
|
</label>
|
|
158
|
-
<span>{{ metric.subRowContent.resourceName }}</span>
|
|
158
|
+
<span :data-testid="`resource-metrics-name-${metric.subRowContent.resourceName}`">{{ metric.subRowContent.resourceName }}</span>
|
|
159
159
|
</div>
|
|
160
160
|
</div>
|
|
161
161
|
</div>
|
|
@@ -173,7 +173,7 @@ export default {
|
|
|
173
173
|
<label class="text-label">
|
|
174
174
|
{{ current.targetName }}:
|
|
175
175
|
</label>
|
|
176
|
-
<span>{{ current.targetValue }}</span>
|
|
176
|
+
<span :data-testid="`current-metrics-${current.targetName}-${metric.subRowContent.resourceName}`">{{ current.targetValue }}</span>
|
|
177
177
|
</div>
|
|
178
178
|
</div>
|
|
179
179
|
</div>
|
|
@@ -339,9 +339,10 @@ export default {
|
|
|
339
339
|
pool._clusterSpec = mp;
|
|
340
340
|
|
|
341
341
|
return {
|
|
342
|
-
poolId:
|
|
343
|
-
mainRowKey:
|
|
342
|
+
poolId: pool.id,
|
|
343
|
+
mainRowKey: 'isFake',
|
|
344
344
|
pool,
|
|
345
|
+
availableActions: []
|
|
345
346
|
};
|
|
346
347
|
});
|
|
347
348
|
},
|
|
@@ -361,9 +362,10 @@ export default {
|
|
|
361
362
|
const emptyNodePools = this.allNodePools.filter((x) => x.spec.clusterName === this.value.mgmtClusterId && x.spec.quantity === 0);
|
|
362
363
|
|
|
363
364
|
return emptyNodePools.map((np) => ({
|
|
364
|
-
spec:
|
|
365
|
-
mainRowKey:
|
|
366
|
-
pool:
|
|
365
|
+
spec: { nodePoolName: np.id.replace('/', ':') },
|
|
366
|
+
mainRowKey: 'isFake',
|
|
367
|
+
pool: np,
|
|
368
|
+
availableActions: []
|
|
367
369
|
}));
|
|
368
370
|
},
|
|
369
371
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* eslint-disable jest/no-hooks */
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import ClusterRoleTemplateBinding from '@shell/edit/management.cattle.io.clusterroletemplatebinding.vue';
|
|
4
|
+
import Banner from '@components/Banner/Banner.vue';
|
|
5
|
+
import CruResource from '@shell/components/CruResource';
|
|
6
|
+
|
|
7
|
+
describe('view: management.cattle.io.clusterroletemplatebinding should', () => {
|
|
8
|
+
let wrapper: any;
|
|
9
|
+
|
|
10
|
+
const stubs = {
|
|
11
|
+
ClusterPermissionsEditor: true,
|
|
12
|
+
Loading: true
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const requiredSetup = () => ({
|
|
16
|
+
// Remove all these mocks after migration to Vue 2.7/3 due mixin logic
|
|
17
|
+
mocks: {
|
|
18
|
+
$store: {
|
|
19
|
+
getters: {
|
|
20
|
+
currentStore: () => 'current_store',
|
|
21
|
+
'current_store/schemaFor': jest.fn(),
|
|
22
|
+
'current_store/all': jest.fn(),
|
|
23
|
+
currentCluster: { id: 'my-cluster' },
|
|
24
|
+
currentProduct: { inStore: 'whatever' },
|
|
25
|
+
'i18n/t': (val) => val,
|
|
26
|
+
'i18n/exists': jest.fn(),
|
|
27
|
+
},
|
|
28
|
+
dispatch: { 'management/findAll': () => ([]) }
|
|
29
|
+
},
|
|
30
|
+
$fetchState: { pending: false },
|
|
31
|
+
$route: { query: { AS: '' } },
|
|
32
|
+
$router: {
|
|
33
|
+
applyQuery: jest.fn(),
|
|
34
|
+
replace: jest.fn()
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
propsData: { value: {} },
|
|
38
|
+
stubs,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
wrapper.destroy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should only show one error banner', async() => {
|
|
46
|
+
const errors = ['mistake!'];
|
|
47
|
+
|
|
48
|
+
wrapper = mount(ClusterRoleTemplateBinding, { ...requiredSetup() });
|
|
49
|
+
|
|
50
|
+
const cruResourceElem = wrapper.findComponent(CruResource);
|
|
51
|
+
|
|
52
|
+
await cruResourceElem.vm.$emit('error', errors);
|
|
53
|
+
|
|
54
|
+
const bannerElems = wrapper.findAllComponents(Banner);
|
|
55
|
+
|
|
56
|
+
expect(bannerElems).toHaveLength(1);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -25,9 +25,10 @@ describe('view Namespace should', () => {
|
|
|
25
25
|
$router: { applyQuery: {} },
|
|
26
26
|
$store: {
|
|
27
27
|
getters: {
|
|
28
|
-
'i18n/t':
|
|
29
|
-
'management/all':
|
|
30
|
-
currentProduct:
|
|
28
|
+
'i18n/t': jest.fn(),
|
|
29
|
+
'management/all': () => ([project]),
|
|
30
|
+
currentProduct: jest.fn(),
|
|
31
|
+
isStandaloneHarvester: false
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
},
|
|
@@ -36,6 +37,7 @@ describe('view Namespace should', () => {
|
|
|
36
37
|
ContainerResourceLimit: { template: '<div data-testid="limits"></div>' }, // Ensure value to be added to component
|
|
37
38
|
NameNsDescription: true,
|
|
38
39
|
Tab: true,
|
|
40
|
+
ResourceTabs: true
|
|
39
41
|
}
|
|
40
42
|
});
|
|
41
43
|
|