@rancher/shell 0.3.24 → 0.3.26
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/themes/_light.scss +1 -1
- package/assets/translations/en-us.yaml +36 -7
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/ClusterIconMenu.vue +143 -0
- package/components/CruResource.vue +10 -1
- package/components/ExplorerProjectsNamespaces.vue +11 -1
- package/components/FixedBanner.vue +17 -1
- package/components/Markdown.vue +1 -1
- package/components/Questions/__tests__/Yaml.test.ts +3 -2
- package/components/SortableTable/index.vue +3 -2
- package/components/__tests__/ProjectRow.test.ts +63 -0
- package/components/auth/RoleDetailEdit.vue +19 -2
- package/components/auth/__tests__/RoleDetailEdit.test.ts +41 -0
- 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/ResourceQuota/ProjectRow.vue +6 -2
- 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 +25 -27
- package/components/nav/Header.vue +12 -5
- package/components/nav/Pinned.vue +47 -0
- package/components/nav/TopLevelMenu.vue +233 -60
- package/components/nav/Type.vue +57 -3
- 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/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/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 +6 -0
- 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 +211 -599
- 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/index.js +5 -5
- package/layouts/default.vue +6 -6
- 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/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/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/about.vue +2 -0
- package/pages/auth/setup.vue +5 -4
- 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/plugins/dashboard-store/normalize.js +4 -4
- 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/rancher-components/Form/LabeledInput/LabeledInput.vue +0 -8
- package/rancher-components/Form/Radio/RadioButton.test.ts +3 -7
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +2 -2
- package/store/index.js +4 -0
- package/store/prefs.js +1 -0
- package/types/shell/index.d.ts +13 -4
- package/utils/__tests__/cluster.test.ts +55 -0
- package/utils/__tests__/object.test.ts +21 -2
- package/utils/cluster.js +47 -1
- package/utils/object.js +12 -5
- 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/yarn-error.log +16 -16
- package/components/ClusterProviderIconMenu.vue +0 -161
- 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
|
@@ -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 })
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Select from '@shell/components/form/Select';
|
|
3
3
|
import UnitInput from '@shell/components/form/UnitInput';
|
|
4
4
|
import { ROW_COMPUTED } from './shared';
|
|
5
|
+
import Vue from 'vue';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
components: { Select, UnitInput },
|
|
@@ -57,10 +58,10 @@ export default {
|
|
|
57
58
|
|
|
58
59
|
updateQuotaLimit(prop, type, val) {
|
|
59
60
|
if (!this.value.spec[prop]) {
|
|
60
|
-
this.value.spec
|
|
61
|
+
Vue.set(this.value.spec, prop, { limit: { } });
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
this.value.spec[prop].limit
|
|
64
|
+
Vue.set(this.value.spec[prop].limit, type, val);
|
|
64
65
|
}
|
|
65
66
|
},
|
|
66
67
|
};
|
|
@@ -75,6 +76,7 @@ export default {
|
|
|
75
76
|
:mode="mode"
|
|
76
77
|
:value="type"
|
|
77
78
|
:options="types"
|
|
79
|
+
data-testid="projectrow-type-input"
|
|
78
80
|
@input="updateType($event)"
|
|
79
81
|
/>
|
|
80
82
|
<UnitInput
|
|
@@ -86,6 +88,7 @@ export default {
|
|
|
86
88
|
:input-exponent="typeOption.inputExponent"
|
|
87
89
|
:base-unit="typeOption.baseUnit"
|
|
88
90
|
:output-modifier="true"
|
|
91
|
+
data-testid="projectrow-project-quota-input"
|
|
89
92
|
@input="updateQuotaLimit('resourceQuota', type, $event)"
|
|
90
93
|
/>
|
|
91
94
|
<UnitInput
|
|
@@ -96,6 +99,7 @@ export default {
|
|
|
96
99
|
:input-exponent="typeOption.inputExponent"
|
|
97
100
|
:base-unit="typeOption.baseUnit"
|
|
98
101
|
:output-modifier="true"
|
|
102
|
+
data-testid="projectrow-namespace-quota-input"
|
|
99
103
|
@input="updateQuotaLimit('namespaceDefaultResourceQuota', type, $event)"
|
|
100
104
|
/>
|
|
101
105
|
</div>
|
|
@@ -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
|
},
|
|
@@ -140,7 +144,7 @@ export default {
|
|
|
140
144
|
peek($event) {
|
|
141
145
|
// Add active class to the current header if click on chevron icon
|
|
142
146
|
$event.target.parentElement.classList.remove('active');
|
|
143
|
-
if (this.hasActiveRoute()) {
|
|
147
|
+
if (this.hasActiveRoute() && this.isExpanded) {
|
|
144
148
|
$event.target.parentElement.classList.add('active');
|
|
145
149
|
}
|
|
146
150
|
this.isExpanded = !this.isExpanded;
|
|
@@ -199,7 +203,7 @@ export default {
|
|
|
199
203
|
<template>
|
|
200
204
|
<div
|
|
201
205
|
class="accordion"
|
|
202
|
-
:class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren}"
|
|
206
|
+
:class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren, 'group-highlight': isGroupActive}"
|
|
203
207
|
>
|
|
204
208
|
<div
|
|
205
209
|
v-if="showHeader"
|
|
@@ -284,15 +288,15 @@ export default {
|
|
|
284
288
|
color: var(--body-text);
|
|
285
289
|
user-select: none;
|
|
286
290
|
text-transform: none;
|
|
287
|
-
font-size:
|
|
291
|
+
font-size: 14px;
|
|
288
292
|
}
|
|
289
293
|
|
|
290
294
|
> A {
|
|
291
295
|
display: block;
|
|
292
296
|
padding-left: 16px;
|
|
293
297
|
&:hover{
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
text-decoration: none;
|
|
299
|
+
}
|
|
296
300
|
&:focus{
|
|
297
301
|
outline:none;
|
|
298
302
|
}
|
|
@@ -300,22 +304,26 @@ export default {
|
|
|
300
304
|
text-transform: none;
|
|
301
305
|
}
|
|
302
306
|
}
|
|
303
|
-
&.active {
|
|
304
|
-
background-color: var(--nav-active);
|
|
305
|
-
}
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
.accordion {
|
|
309
310
|
.header {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
}
|
|
313
319
|
|
|
314
|
-
> I {
|
|
315
320
|
&:hover {
|
|
316
|
-
background-color: var(--
|
|
321
|
+
background-color: var(--primary-hover-bg);
|
|
317
322
|
}
|
|
318
323
|
}
|
|
324
|
+
&:hover:not(.active) {
|
|
325
|
+
background-color: var(--nav-hover);
|
|
326
|
+
}
|
|
319
327
|
}
|
|
320
328
|
}
|
|
321
329
|
|
|
@@ -340,15 +348,15 @@ export default {
|
|
|
340
348
|
padding: 10px 10px 9px 7px;
|
|
341
349
|
user-select: none;
|
|
342
350
|
}
|
|
343
|
-
|
|
344
|
-
&:has(> a.nuxt-link-active) {
|
|
345
|
-
background: var(--nav-active);
|
|
346
|
-
}
|
|
347
351
|
}
|
|
348
352
|
|
|
349
353
|
> .body {
|
|
350
354
|
margin-left: 0;
|
|
351
355
|
}
|
|
356
|
+
|
|
357
|
+
&.group-highlight {
|
|
358
|
+
background: var(--nav-active);
|
|
359
|
+
}
|
|
352
360
|
}
|
|
353
361
|
|
|
354
362
|
&.depth-1 {
|
|
@@ -380,15 +388,6 @@ export default {
|
|
|
380
388
|
}
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
|
-
|
|
384
|
-
&.expanded:has(> .active),
|
|
385
|
-
&.expanded:has(> ul li.nuxt-link-active) {
|
|
386
|
-
background: var(--nav-active);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
&.expanded:has(> ul li.root) {
|
|
390
|
-
background: transparent;
|
|
391
|
-
}
|
|
392
391
|
}
|
|
393
392
|
|
|
394
393
|
.body ::v-deep > .child.nuxt-link-active,
|
|
@@ -427,5 +426,4 @@ export default {
|
|
|
427
426
|
}
|
|
428
427
|
}
|
|
429
428
|
}
|
|
430
|
-
|
|
431
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');
|
|
@@ -720,6 +724,9 @@ export default {
|
|
|
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
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
// Allow the user to pin a cluster by clicking it.
|
|
3
|
+
export default {
|
|
4
|
+
props: {
|
|
5
|
+
cluster: {
|
|
6
|
+
type: Object,
|
|
7
|
+
required: true,
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
computed: {
|
|
12
|
+
pinned() {
|
|
13
|
+
return this.cluster.pinned;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
methods: {
|
|
18
|
+
toggle() {
|
|
19
|
+
if ( this.pinned ) {
|
|
20
|
+
this.cluster.unpin();
|
|
21
|
+
} else {
|
|
22
|
+
this.cluster.pin();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<i
|
|
31
|
+
:tabindex="0"
|
|
32
|
+
:aria-checked="!!pinned"
|
|
33
|
+
class="pin icon"
|
|
34
|
+
:class="{'icon-pin-outlined': !pinned, 'icon-pin': pinned}"
|
|
35
|
+
aria-role="button"
|
|
36
|
+
@click.stop.prevent="toggle"
|
|
37
|
+
@keydown.enter.prevent="toggle"
|
|
38
|
+
@keydown.space.prevent="toggle"
|
|
39
|
+
/>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style lang="scss" scoped>
|
|
43
|
+
.icon {
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
transform: scaleX(-1);
|
|
46
|
+
}
|
|
47
|
+
</style>
|