@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
|
@@ -5,7 +5,18 @@ export default {
|
|
|
5
5
|
|
|
6
6
|
methods: {
|
|
7
7
|
async login() {
|
|
8
|
-
const
|
|
8
|
+
const { requestId, publicKey, responseType } = this.$route.query;
|
|
9
|
+
|
|
10
|
+
const res = await this.$store.dispatch('auth/login', {
|
|
11
|
+
provider: this.name,
|
|
12
|
+
body: {
|
|
13
|
+
finalRedirectUrl: window.location.origin,
|
|
14
|
+
requestId,
|
|
15
|
+
publicKey,
|
|
16
|
+
responseType
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
9
20
|
const { idpRedirectUrl } = res;
|
|
10
21
|
|
|
11
22
|
window.location.href = idpRedirectUrl;
|
|
@@ -149,11 +149,10 @@ export default {
|
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const newOption = this.options.find((opt) => isEqual(this.reduce(option), this.reduce(opt)));
|
|
152
|
+
// This check is only needed if its possible for an option's label to change without the option's value changing - we can skip this if options are just strings or numbers
|
|
153
|
+
// HOWEVER even if strings are passed to v-select the 'option' in the slot is normalized to {label: <option>} so we have to check the options prop here instead of the 'option' itself
|
|
154
|
+
if (typeof this.options[0] === 'object') {
|
|
155
|
+
const newOption = this.getUpdatedOption(option);
|
|
157
156
|
|
|
158
157
|
if (newOption) {
|
|
159
158
|
const label = get(newOption, this.optionLabel);
|
|
@@ -178,6 +177,14 @@ export default {
|
|
|
178
177
|
}
|
|
179
178
|
},
|
|
180
179
|
|
|
180
|
+
// If the option's label changed in parent but value did not, the label wont be automatically updated here
|
|
181
|
+
// Ensure that the label being shown is still present in the options prop and find the new one if not
|
|
182
|
+
getUpdatedOption(option) {
|
|
183
|
+
const isOutdated = this.options && !this.options.find((opt) => option[this.optionLabel] === opt[this.optionLabel]);
|
|
184
|
+
|
|
185
|
+
return isOutdated ? this.options.find((opt) => isEqual(this.reduce(option), this.reduce(opt))) : undefined;
|
|
186
|
+
},
|
|
187
|
+
|
|
181
188
|
positionDropdown(dropdownList, component, { width }) {
|
|
182
189
|
calculatePosition(dropdownList, component, width, this.placement);
|
|
183
190
|
},
|
|
@@ -198,7 +198,7 @@ export default {
|
|
|
198
198
|
async principalProperty() {
|
|
199
199
|
const principal = await this.principal;
|
|
200
200
|
|
|
201
|
-
return principal
|
|
201
|
+
return principal?.principalType === 'group' ? 'groupPrincipalId' : 'userPrincipalId';
|
|
202
202
|
},
|
|
203
203
|
|
|
204
204
|
onAdd(principalId) {
|
|
@@ -56,8 +56,13 @@ export default {
|
|
|
56
56
|
},
|
|
57
57
|
|
|
58
58
|
async fetch() {
|
|
59
|
+
const roleBindingRequestParams = { type: this.type, opt: { force: true } };
|
|
60
|
+
|
|
61
|
+
if (this.type === NORMAN.PROJECT_ROLE_TEMPLATE_BINDING && this.parentId) {
|
|
62
|
+
Object.assign(roleBindingRequestParams, { opt: { filter: { projectId: this.parentId.split('/').join(':') } } });
|
|
63
|
+
}
|
|
59
64
|
const userHydration = [
|
|
60
|
-
this.schema ? this.$store.dispatch(`rancher/findAll`,
|
|
65
|
+
this.schema ? this.$store.dispatch(`rancher/findAll`, roleBindingRequestParams) : [],
|
|
61
66
|
this.$store.dispatch('rancher/findAll', { type: NORMAN.PRINCIPAL }),
|
|
62
67
|
this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.ROLE_TEMPLATE }),
|
|
63
68
|
this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.USER })
|
|
@@ -477,6 +477,7 @@ export default {
|
|
|
477
477
|
<div :class="firstCol">
|
|
478
478
|
<LabeledSelect
|
|
479
479
|
v-model="selected"
|
|
480
|
+
data-testid="auth-secret-select"
|
|
480
481
|
:mode="mode"
|
|
481
482
|
:label-key="labelKey"
|
|
482
483
|
:loading="$fetchState.pending"
|
|
@@ -488,6 +489,7 @@ export default {
|
|
|
488
489
|
<div :class="moreCols">
|
|
489
490
|
<LabeledInput
|
|
490
491
|
v-model="publicKey"
|
|
492
|
+
data-testid="auth-secret-ssh-public-key"
|
|
491
493
|
:mode="mode"
|
|
492
494
|
type="multiline"
|
|
493
495
|
label-key="selectOrCreateAuthSecret.ssh.publicKey"
|
|
@@ -496,6 +498,7 @@ export default {
|
|
|
496
498
|
<div :class="moreCols">
|
|
497
499
|
<LabeledInput
|
|
498
500
|
v-model="privateKey"
|
|
501
|
+
data-testid="auth-secret-ssh-private-key"
|
|
499
502
|
:mode="mode"
|
|
500
503
|
type="multiline"
|
|
501
504
|
label-key="selectOrCreateAuthSecret.ssh.privateKey"
|
|
@@ -506,6 +509,7 @@ export default {
|
|
|
506
509
|
<div :class="moreCols">
|
|
507
510
|
<LabeledInput
|
|
508
511
|
v-model="publicKey"
|
|
512
|
+
data-testid="auth-secret-basic-public-key"
|
|
509
513
|
:mode="mode"
|
|
510
514
|
label-key="selectOrCreateAuthSecret.basic.username"
|
|
511
515
|
/>
|
|
@@ -513,6 +517,7 @@ export default {
|
|
|
513
517
|
<div :class="moreCols">
|
|
514
518
|
<LabeledInput
|
|
515
519
|
v-model="privateKey"
|
|
520
|
+
data-testid="auth-secret-basic-private-key"
|
|
516
521
|
:mode="mode"
|
|
517
522
|
type="password"
|
|
518
523
|
label-key="selectOrCreateAuthSecret.basic.password"
|
|
@@ -523,6 +528,7 @@ export default {
|
|
|
523
528
|
<div :class="moreCols">
|
|
524
529
|
<LabeledInput
|
|
525
530
|
v-model="publicKey"
|
|
531
|
+
data-testid="auth-secret-s3-public-key"
|
|
526
532
|
:mode="mode"
|
|
527
533
|
label-key="selectOrCreateAuthSecret.s3.accessKey"
|
|
528
534
|
/>
|
|
@@ -530,6 +536,7 @@ export default {
|
|
|
530
536
|
<div :class="moreCols">
|
|
531
537
|
<LabeledInput
|
|
532
538
|
v-model="privateKey"
|
|
539
|
+
data-testid="auth-secret-s3-private-key"
|
|
533
540
|
:mode="mode"
|
|
534
541
|
type="password"
|
|
535
542
|
label-key="selectOrCreateAuthSecret.s3.secretKey"
|
|
@@ -8,7 +8,8 @@ describe('component: KeyValue', () => {
|
|
|
8
8
|
const wrapper = mount(KeyValue, {
|
|
9
9
|
propsData: { value: { value } },
|
|
10
10
|
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
|
|
11
|
-
directives: { t }
|
|
11
|
+
directives: { t },
|
|
12
|
+
stubs: { CodeMirror: true }
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
const inputValue = wrapper.find('textarea').element as HTMLInputElement;
|
|
@@ -24,7 +25,8 @@ describe('component: KeyValue', () => {
|
|
|
24
25
|
valueMarkdownMultiline: true,
|
|
25
26
|
},
|
|
26
27
|
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
|
|
27
|
-
directives: { t }
|
|
28
|
+
directives: { t },
|
|
29
|
+
stubs: { CodeMirror: true }
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
const inputFieldTextArea = wrapper.find('textarea').element;
|
|
@@ -41,7 +43,8 @@ describe('component: KeyValue', () => {
|
|
|
41
43
|
valueMarkdownMultiline: false,
|
|
42
44
|
},
|
|
43
45
|
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
|
|
44
|
-
directives: { t }
|
|
46
|
+
directives: { t },
|
|
47
|
+
stubs: { CodeMirror: true }
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
const inputFieldTextArea = wrapper.find('[data-testid="text-area-auto-grow"]');
|
|
@@ -133,6 +133,24 @@ describe('component: LabeledSelect', () => {
|
|
|
133
133
|
// Component is from a library and class is not going to be changed
|
|
134
134
|
expect(wrapper.find('.vs__selected').text()).toBe(translation);
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
it.each([
|
|
138
|
+
[['a'], 'b', 0],
|
|
139
|
+
[[{ value: 'a', label: 'A' }], { value: 'a', label: 'B' }, 4]
|
|
140
|
+
])('should only check for a new label if options are objects', async(options: any[], newOption, checkUpdateCalled: number) => {
|
|
141
|
+
const spyUpdatedOption = jest.spyOn(LabeledSelect.methods, 'getUpdatedOption');
|
|
142
|
+
|
|
143
|
+
const wrapper = mount(LabeledSelect, {
|
|
144
|
+
propsData: {
|
|
145
|
+
value: 'a',
|
|
146
|
+
options
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await wrapper.setProps({ options: [newOption] });
|
|
151
|
+
|
|
152
|
+
expect(spyUpdatedOption).toHaveBeenCalledTimes(checkUpdateCalled);
|
|
153
|
+
});
|
|
136
154
|
});
|
|
137
155
|
});
|
|
138
156
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { POD } from '@shell/config/types';
|
|
3
2
|
export default {
|
|
4
3
|
name: 'PodsUsage',
|
|
5
4
|
props: {
|
|
@@ -8,47 +7,23 @@ export default {
|
|
|
8
7
|
required: true
|
|
9
8
|
},
|
|
10
9
|
},
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
-
},
|
|
17
|
-
methods: {
|
|
18
|
-
async startDelayedLoading() {
|
|
19
|
-
const id = this.row?.mgmt?.id;
|
|
20
|
-
|
|
21
|
-
if (this.row?.isReady && id) {
|
|
22
|
-
const req = await this.$store.dispatch('management/request', { url: `/k8s/clusters/${ id }/v1/counts` });
|
|
10
|
+
computed: {
|
|
11
|
+
podsUsage() {
|
|
12
|
+
const usedPods = this.row?.mgmt?.status?.requested?.pods;
|
|
13
|
+
const totalPods = this.row?.mgmt?.status?.allocatable?.pods;
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const totalPods = this.row?.mgmt?.status?.allocatable?.pods;
|
|
27
|
-
|
|
28
|
-
if (totalPods) {
|
|
29
|
-
this.podsUsage = `${ usedPods }/${ totalPods }`;
|
|
30
|
-
} else {
|
|
31
|
-
this.podsUsage = '—';
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
this.loading = false;
|
|
35
|
-
this.podsUsage = '—';
|
|
15
|
+
if (!this.row?.isReady || !totalPods) {
|
|
16
|
+
return '—';
|
|
36
17
|
}
|
|
18
|
+
|
|
19
|
+
return `${ usedPods || 0 }/${ totalPods }`;
|
|
37
20
|
}
|
|
38
|
-
}
|
|
21
|
+
}
|
|
39
22
|
};
|
|
40
23
|
</script>
|
|
41
24
|
|
|
42
25
|
<template>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
class="icon icon-spinner icon-spin"
|
|
46
|
-
/>
|
|
47
|
-
<p v-else>
|
|
48
|
-
{{ podsUsage }}
|
|
26
|
+
<p>
|
|
27
|
+
<span>{{ podsUsage }}</span>
|
|
49
28
|
</p>
|
|
50
29
|
</template>
|
|
51
|
-
|
|
52
|
-
<style lang="scss" scoped>
|
|
53
|
-
|
|
54
|
-
</style>
|
|
@@ -11,12 +11,15 @@ export default {
|
|
|
11
11
|
computed: {
|
|
12
12
|
|
|
13
13
|
boundRoles() {
|
|
14
|
-
|
|
14
|
+
// need to use getter to fetch all NORMAN.PRINCIPAL, otherwise `rancher/byId` is not reactive...
|
|
15
|
+
const principals = this.$store.getters['rancher/all'](NORMAN.PRINCIPAL);
|
|
15
16
|
const globalRoleBindings = this.$store.getters['management/all'](MANAGEMENT.GLOBAL_ROLE_BINDING);
|
|
16
17
|
|
|
18
|
+
const principal = principals.find((x) => x.id === this.value);
|
|
19
|
+
|
|
17
20
|
return globalRoleBindings
|
|
18
21
|
// Bindings for this group
|
|
19
|
-
.filter((globalRoleBinding) => globalRoleBinding.groupPrincipalName === principal
|
|
22
|
+
.filter((globalRoleBinding) => globalRoleBinding.groupPrincipalName === principal?.id)
|
|
20
23
|
// Display name of role associated with binding
|
|
21
24
|
.map((binding) => {
|
|
22
25
|
const role = this.$store.getters['management/byId'](MANAGEMENT.GLOBAL_ROLE, binding.globalRoleName);
|
|
@@ -35,9 +38,9 @@ export default {
|
|
|
35
38
|
<template>
|
|
36
39
|
<div class="pgb">
|
|
37
40
|
<template v-for="(role, i) in boundRoles">
|
|
38
|
-
<nuxt-link
|
|
39
|
-
:key="role.id"
|
|
40
|
-
:to="role.detailLocation"
|
|
41
|
+
<nuxt-link
|
|
42
|
+
:key="role.id"
|
|
43
|
+
:to="role.detailLocation"
|
|
41
44
|
>
|
|
42
45
|
{{ role.label }}
|
|
43
46
|
</nuxt-link>
|
|
@@ -2,37 +2,54 @@ import { mount } from '@vue/test-utils';
|
|
|
2
2
|
import PodsUsage from '@shell/components/formatter/PodsUsage.vue';
|
|
3
3
|
|
|
4
4
|
describe('component: PodsUsage', () => {
|
|
5
|
-
it('should
|
|
5
|
+
it('should display podsUsage value', () => {
|
|
6
6
|
const wrapper = mount(PodsUsage, {
|
|
7
|
-
propsData: {
|
|
8
|
-
|
|
7
|
+
propsData: {
|
|
8
|
+
row: {
|
|
9
|
+
isReady: true,
|
|
10
|
+
mgmt: {
|
|
11
|
+
status: {
|
|
12
|
+
requested: { pods: 10 },
|
|
13
|
+
allocatable: { pods: 20 }
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
mocks: { $store: { dispatch: { 'management/request': jest.fn() } } }
|
|
9
19
|
});
|
|
10
20
|
|
|
11
|
-
const element = wrapper.find('p')
|
|
21
|
+
const { element } = wrapper.find('p');
|
|
12
22
|
|
|
13
|
-
expect(element).
|
|
23
|
+
expect(element.textContent).toBeDefined();
|
|
24
|
+
expect(element.textContent).toBe('10/20');
|
|
14
25
|
});
|
|
15
|
-
|
|
16
|
-
it('should display spinning icon', () => {
|
|
26
|
+
it('should display dash when there are no totalPods', () => {
|
|
17
27
|
const wrapper = mount(PodsUsage, {
|
|
18
|
-
propsData: {
|
|
19
|
-
|
|
28
|
+
propsData: {
|
|
29
|
+
row: {
|
|
30
|
+
isReady: true,
|
|
31
|
+
mgmt: {
|
|
32
|
+
status: {
|
|
33
|
+
requested: { pods: 10 },
|
|
34
|
+
allocatable: { pods: 0 }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
mocks: { $store: { dispatch: { 'management/request': jest.fn() } } }
|
|
20
40
|
});
|
|
21
41
|
|
|
22
|
-
const element = wrapper.find('
|
|
42
|
+
const { element } = wrapper.find('p');
|
|
23
43
|
|
|
24
|
-
expect(element).toBeDefined();
|
|
44
|
+
expect(element.textContent).toBeDefined();
|
|
45
|
+
expect(element.textContent).toBe('—');
|
|
25
46
|
});
|
|
47
|
+
it('should display a dash when there is no management cluster ti query for status', () => {
|
|
48
|
+
const wrapper = mount(PodsUsage, { propsData: { row: { isReady: true } } });
|
|
26
49
|
|
|
27
|
-
|
|
28
|
-
const wrapper = mount(PodsUsage, {
|
|
29
|
-
propsData: { row: { isReady: true } },
|
|
30
|
-
data: () => ({ loading: false }),
|
|
31
|
-
mocks: { $store: { dispatch: { 'management/request': jest.fn() } } }
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const element = wrapper.find('p').element;
|
|
50
|
+
const { element } = wrapper.find('p');
|
|
35
51
|
|
|
36
52
|
expect(element.textContent).toBeDefined();
|
|
53
|
+
expect(element.textContent).toBe('—');
|
|
37
54
|
});
|
|
38
55
|
});
|
package/components/nav/Group.vue
CHANGED
|
@@ -49,6 +49,10 @@ export default {
|
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
computed: {
|
|
52
|
+
isGroupActive() {
|
|
53
|
+
return this.isOverview || (this.hasActiveRoute() && this.isExpanded && this.showHeader);
|
|
54
|
+
},
|
|
55
|
+
|
|
52
56
|
hasChildren() {
|
|
53
57
|
return this.group.children?.length > 0;
|
|
54
58
|
},
|
|
@@ -69,7 +73,7 @@ export default {
|
|
|
69
73
|
if (overviewRoute && grp.overview) {
|
|
70
74
|
const route = this.$router.resolve(overviewRoute || {});
|
|
71
75
|
|
|
72
|
-
return this.$route.fullPath === route?.route?.fullPath;
|
|
76
|
+
return this.$route.fullPath.split('#')[0] === route?.route?.fullPath;
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
@@ -96,8 +100,14 @@ export default {
|
|
|
96
100
|
// Don't auto-select first group entry if we're already expanded and contain the currently-selected nav item
|
|
97
101
|
if (this.hasActiveRoute() && this.isExpanded) {
|
|
98
102
|
return;
|
|
99
|
-
}
|
|
103
|
+
} else {
|
|
104
|
+
// Remove all active class if click on group header and not active route
|
|
105
|
+
const headerEl = document.querySelectorAll('.header');
|
|
100
106
|
|
|
107
|
+
headerEl.forEach((el) => {
|
|
108
|
+
el.classList.remove('active');
|
|
109
|
+
});
|
|
110
|
+
}
|
|
101
111
|
this.expandGroup();
|
|
102
112
|
|
|
103
113
|
const items = this.group[this.childrenKey];
|
|
@@ -132,6 +142,11 @@ export default {
|
|
|
132
142
|
|
|
133
143
|
// User clicked on the expander icon, so toggle the expansion so the user can see inside the group
|
|
134
144
|
peek($event) {
|
|
145
|
+
// Add active class to the current header if click on chevron icon
|
|
146
|
+
$event.target.parentElement.classList.remove('active');
|
|
147
|
+
if (this.hasActiveRoute() && this.isExpanded) {
|
|
148
|
+
$event.target.parentElement.classList.add('active');
|
|
149
|
+
}
|
|
135
150
|
this.isExpanded = !this.isExpanded;
|
|
136
151
|
$event.stopPropagation();
|
|
137
152
|
},
|
|
@@ -188,7 +203,7 @@ export default {
|
|
|
188
203
|
<template>
|
|
189
204
|
<div
|
|
190
205
|
class="accordion"
|
|
191
|
-
:class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren}"
|
|
206
|
+
:class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren, 'group-highlight': isGroupActive}"
|
|
192
207
|
>
|
|
193
208
|
<div
|
|
194
209
|
v-if="showHeader"
|
|
@@ -212,7 +227,7 @@ export default {
|
|
|
212
227
|
<i
|
|
213
228
|
v-if="!onlyHasOverview && canCollapse"
|
|
214
229
|
class="icon toggle"
|
|
215
|
-
:class="{'icon-chevron-
|
|
230
|
+
:class="{'icon-chevron-right': !isExpanded, 'icon-chevron-down': isExpanded}"
|
|
216
231
|
@click="peek($event, true)"
|
|
217
232
|
/>
|
|
218
233
|
</div>
|
|
@@ -267,8 +282,9 @@ export default {
|
|
|
267
282
|
position: relative;
|
|
268
283
|
cursor: pointer;
|
|
269
284
|
color: var(--body-text);
|
|
285
|
+
height: 33px;
|
|
270
286
|
|
|
271
|
-
|
|
287
|
+
H6 {
|
|
272
288
|
color: var(--body-text);
|
|
273
289
|
user-select: none;
|
|
274
290
|
text-transform: none;
|
|
@@ -277,39 +293,37 @@ export default {
|
|
|
277
293
|
|
|
278
294
|
> A {
|
|
279
295
|
display: block;
|
|
280
|
-
padding-left:
|
|
296
|
+
padding-left: 16px;
|
|
281
297
|
&:hover{
|
|
282
|
-
|
|
283
|
-
|
|
298
|
+
text-decoration: none;
|
|
299
|
+
}
|
|
284
300
|
&:focus{
|
|
285
301
|
outline:none;
|
|
286
302
|
}
|
|
287
303
|
> H6 {
|
|
288
|
-
font-size: 14px;
|
|
289
304
|
text-transform: none;
|
|
290
305
|
}
|
|
291
306
|
}
|
|
292
|
-
|
|
293
|
-
&.active {
|
|
294
|
-
background-color: var(--nav-active);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
.body {
|
|
299
|
-
margin-left: 10px;
|
|
300
307
|
}
|
|
301
308
|
|
|
302
309
|
.accordion {
|
|
303
310
|
.header {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
311
|
+
&.active {
|
|
312
|
+
color: var(--primary-hover-text);
|
|
313
|
+
background-color: var(--primary-hover-bg);
|
|
314
|
+
|
|
315
|
+
h6 {
|
|
316
|
+
font-weight: bold;
|
|
317
|
+
color: var(--primary-hover-text);
|
|
318
|
+
}
|
|
307
319
|
|
|
308
|
-
> I {
|
|
309
320
|
&:hover {
|
|
310
|
-
background-color: var(--
|
|
321
|
+
background-color: var(--primary-hover-bg);
|
|
311
322
|
}
|
|
312
323
|
}
|
|
324
|
+
&:hover:not(.active) {
|
|
325
|
+
background-color: var(--nav-hover);
|
|
326
|
+
}
|
|
313
327
|
}
|
|
314
328
|
}
|
|
315
329
|
|
|
@@ -323,16 +337,15 @@ export default {
|
|
|
323
337
|
}
|
|
324
338
|
|
|
325
339
|
> H6 {
|
|
326
|
-
font-size: 14px;
|
|
327
340
|
text-transform: none;
|
|
328
|
-
padding-left:
|
|
341
|
+
padding-left: 16px;
|
|
329
342
|
}
|
|
330
343
|
|
|
331
344
|
> I {
|
|
332
345
|
position: absolute;
|
|
333
346
|
right: 0;
|
|
334
347
|
top: 0;
|
|
335
|
-
padding: 10px
|
|
348
|
+
padding: 10px 10px 9px 7px;
|
|
336
349
|
user-select: none;
|
|
337
350
|
}
|
|
338
351
|
}
|
|
@@ -340,24 +353,27 @@ export default {
|
|
|
340
353
|
> .body {
|
|
341
354
|
margin-left: 0;
|
|
342
355
|
}
|
|
356
|
+
|
|
357
|
+
&.group-highlight {
|
|
358
|
+
background: var(--nav-active);
|
|
359
|
+
}
|
|
343
360
|
}
|
|
344
361
|
|
|
345
362
|
&.depth-1 {
|
|
346
363
|
> .header {
|
|
364
|
+
padding-left: 20px;
|
|
347
365
|
> H6 {
|
|
348
|
-
|
|
349
|
-
line-height: 16px;
|
|
366
|
+
line-height: 18px;
|
|
350
367
|
padding: 8px 0 7px 5px !important;
|
|
351
368
|
}
|
|
352
369
|
> I {
|
|
353
|
-
padding:
|
|
370
|
+
padding: 10px 7px 9px 7px !important;
|
|
354
371
|
}
|
|
355
372
|
}
|
|
356
373
|
}
|
|
357
374
|
|
|
358
375
|
&:not(.depth-0) {
|
|
359
376
|
> .header {
|
|
360
|
-
padding-left: 10px;
|
|
361
377
|
> H6 {
|
|
362
378
|
// Child groups that aren't linked themselves
|
|
363
379
|
display: inline-block;
|
|
@@ -374,16 +390,18 @@ export default {
|
|
|
374
390
|
}
|
|
375
391
|
}
|
|
376
392
|
|
|
377
|
-
|
|
378
|
-
|
|
393
|
+
.body ::v-deep > .child.nuxt-link-active,
|
|
394
|
+
.header ::v-deep > .child.nuxt-link-exact-active {
|
|
379
395
|
padding: 0;
|
|
380
396
|
|
|
381
397
|
A, A I {
|
|
382
|
-
color: var(--
|
|
398
|
+
color: var(--primary-hover-text);
|
|
383
399
|
}
|
|
384
400
|
|
|
385
401
|
A {
|
|
386
|
-
|
|
402
|
+
color: var(--primary-hover-text);
|
|
403
|
+
background-color: var(--primary-hover-bg);
|
|
404
|
+
font-weight: bold;
|
|
387
405
|
}
|
|
388
406
|
}
|
|
389
407
|
|
|
@@ -391,11 +409,21 @@ export default {
|
|
|
391
409
|
A {
|
|
392
410
|
border-left: solid 5px transparent;
|
|
393
411
|
line-height: 16px;
|
|
394
|
-
font-size:
|
|
412
|
+
font-size: 14px;
|
|
413
|
+
padding-left: 24px;
|
|
414
|
+
display: flex;
|
|
415
|
+
justify-content: space-between;
|
|
395
416
|
}
|
|
396
417
|
|
|
397
418
|
A:focus {
|
|
398
419
|
outline: none;
|
|
399
420
|
}
|
|
421
|
+
|
|
422
|
+
&.root {
|
|
423
|
+
background: transparent;
|
|
424
|
+
A {
|
|
425
|
+
padding-left: 14px;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
400
428
|
}
|
|
401
429
|
</style>
|
|
@@ -60,8 +60,8 @@ export default {
|
|
|
60
60
|
},
|
|
61
61
|
|
|
62
62
|
computed: {
|
|
63
|
-
...mapGetters(['clusterReady', 'isExplorer', '
|
|
64
|
-
'currentProduct', 'backToRancherLink', 'backToRancherGlobalLink', 'pageActions', 'isSingleProduct', 'isRancherInHarvester']),
|
|
63
|
+
...mapGetters(['clusterReady', 'isExplorer', 'isRancher', 'currentCluster',
|
|
64
|
+
'currentProduct', 'backToRancherLink', 'backToRancherGlobalLink', 'pageActions', 'isSingleProduct', 'isRancherInHarvester', 'showTopLevelMenu']),
|
|
65
65
|
...mapGetters('type-map', ['activeProducts']),
|
|
66
66
|
|
|
67
67
|
appName() {
|
|
@@ -314,7 +314,7 @@ export default {
|
|
|
314
314
|
const enabled = action.enabled ? action.enabled.apply(this, [opts]) : true;
|
|
315
315
|
|
|
316
316
|
if (fn && enabled) {
|
|
317
|
-
fn.apply(this, [opts, []]);
|
|
317
|
+
fn.apply(this, [opts, [], { $route: this.$route }]);
|
|
318
318
|
}
|
|
319
319
|
},
|
|
320
320
|
|
|
@@ -338,7 +338,7 @@ export default {
|
|
|
338
338
|
data-testid="header"
|
|
339
339
|
>
|
|
340
340
|
<div>
|
|
341
|
-
<TopLevelMenu v-if="
|
|
341
|
+
<TopLevelMenu v-if="showTopLevelMenu" />
|
|
342
342
|
</div>
|
|
343
343
|
<div
|
|
344
344
|
class="menu-spacer"
|
|
@@ -706,6 +706,10 @@ export default {
|
|
|
706
706
|
</template>
|
|
707
707
|
|
|
708
708
|
<style lang="scss" scoped>
|
|
709
|
+
$side-menu-logo-margin-left: 5px;
|
|
710
|
+
// It would be nice to grab this from `Group.vue`, but there's margin, padding and border, which is overkill to var
|
|
711
|
+
$side-menu-group-padding-left: 16px;
|
|
712
|
+
|
|
709
713
|
HEADER {
|
|
710
714
|
display: flex;
|
|
711
715
|
z-index: z-index('mainHeader');
|
|
@@ -715,11 +719,14 @@ export default {
|
|
|
715
719
|
}
|
|
716
720
|
|
|
717
721
|
> .menu-spacer {
|
|
718
|
-
flex: 0 0
|
|
722
|
+
flex: 0 0 15px;
|
|
719
723
|
|
|
720
724
|
&.isSingleProduct {
|
|
721
725
|
display: flex;
|
|
722
726
|
justify-content: center;
|
|
727
|
+
// Align the icon with the side nav menu items ($side-menu-group-padding-left)
|
|
728
|
+
// There's margin already in the icon component, so take that in to account ($side-menu-logo-margin-left)
|
|
729
|
+
margin-left: $side-menu-group-padding-left - $side-menu-logo-margin-left;
|
|
723
730
|
}
|
|
724
731
|
}
|
|
725
732
|
|
|
@@ -808,7 +815,7 @@ export default {
|
|
|
808
815
|
display: flex;
|
|
809
816
|
margin-right: 8px;
|
|
810
817
|
height: 55px;
|
|
811
|
-
margin-left:
|
|
818
|
+
margin-left: $side-menu-logo-margin-left;
|
|
812
819
|
max-width: 200px;
|
|
813
820
|
padding: 12px 0;
|
|
814
821
|
}
|