@rancher/shell 3.0.5-rc.8 → 3.0.5
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/_color.scss +4 -1
- package/assets/styles/global/_tooltip.scss +7 -4
- package/assets/styles/themes/_dark.scss +11 -0
- package/assets/styles/themes/_light.scss +13 -1
- package/assets/styles/themes/_modern.scss +22 -0
- package/assets/translations/en-us.yaml +147 -19
- package/assets/translations/zh-hans.yaml +0 -1
- package/chart/monitoring/grafana/index.vue +8 -2
- package/components/ActionMenuShell.vue +3 -1
- package/components/Cron/CronExpressionEditor.vue +299 -0
- package/components/Cron/CronExpressionEditorModal.vue +247 -0
- package/components/Cron/CronTooltip.vue +87 -0
- package/components/Cron/types.ts +13 -0
- package/components/ForceDirectedTreeChart/composable.ts +11 -0
- package/components/PodSecurityAdmission.vue +2 -0
- package/components/PromptModal.vue +1 -1
- package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
- package/components/Resource/Detail/CopyToClipboard.vue +78 -0
- package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
- package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
- package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
- package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
- package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
- package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
- package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
- package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
- package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
- package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
- package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
- package/components/Resource/Detail/Metadata/composables.ts +1 -4
- package/components/Resource/Detail/Metadata/index.vue +1 -0
- package/components/Resource/Detail/Preview/Content.vue +63 -0
- package/components/Resource/Detail/Preview/Preview.vue +128 -0
- package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
- package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
- package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
- package/components/Resource/Detail/SpacedRow.vue +1 -0
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
- package/components/Resource/Detail/TitleBar/composables.ts +1 -3
- package/components/Resource/Detail/TitleBar/index.vue +2 -29
- package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
- package/components/Resource/Detail/ViewOptions/index.vue +41 -0
- package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
- package/components/ResourceDetail/Masthead/legacy.vue +0 -19
- package/components/ResourceDetail/index.vue +1 -26
- package/components/ResourceTable.vue +24 -0
- package/components/SortableTable/index.vue +7 -1
- package/components/SortableTable/paging.js +3 -0
- package/components/Tabbed/Tab.vue +43 -1
- package/components/Tabbed/index.vue +3 -1
- package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
- package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
- package/components/auth/login/saml.vue +86 -0
- package/components/form/LabeledSelect.vue +8 -8
- package/components/form/ProjectMemberEditor.vue +2 -0
- package/components/form/ResourceTabs/composable.ts +54 -0
- package/components/form/ResourceTabs/index.vue +10 -7
- package/components/form/Select.vue +13 -10
- package/components/form/__tests__/LabeledSelect.test.ts +133 -0
- package/components/form/__tests__/Select.test.ts +134 -0
- package/components/nav/Header.vue +6 -5
- package/composables/useExtensionManager.ts +17 -0
- package/config/home-links.js +12 -0
- package/config/labels-annotations.js +0 -1
- package/config/page-actions.js +0 -1
- package/config/product/explorer.js +3 -1
- package/config/product/fleet.js +2 -7
- package/config/product/manager.js +0 -5
- package/config/query-params.js +1 -0
- package/config/router/navigation-guards/clusters.js +2 -1
- package/config/router/navigation-guards/products.js +1 -1
- package/config/store.js +2 -0
- package/core/extension-manager-impl.js +518 -0
- package/core/plugins.js +35 -468
- package/core/types.ts +8 -2
- package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
- package/detail/catalog.cattle.io.app.vue +7 -4
- package/detail/fleet.cattle.io.bundle.vue +1 -5
- package/detail/fleet.cattle.io.cluster.vue +3 -2
- package/detail/fleet.cattle.io.gitrepo.vue +76 -49
- package/detail/fleet.cattle.io.helmop.vue +78 -49
- package/dialog/AddonConfigConfirmationDialog.vue +1 -1
- package/dialog/GenericPrompt.vue +1 -1
- package/dialog/ImportDialog.vue +9 -2
- package/dialog/InstallExtensionDialog.vue +18 -10
- package/dialog/SloDialog.vue +1 -1
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
- package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
- package/edit/auth/oidc.vue +106 -6
- package/edit/auth/saml.vue +5 -5
- package/edit/cloudcredential.vue +31 -17
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
- package/edit/fleet.cattle.io.cluster.vue +19 -0
- package/edit/fleet.cattle.io.gitrepo.vue +23 -16
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
- package/edit/resources.cattle.io.restore.vue +5 -8
- package/initialize/install-plugins.js +1 -3
- package/list/__tests__/workload.test.ts +1 -0
- package/list/workload.vue +8 -1
- package/machine-config/components/GCEImage.vue +6 -5
- package/machine-config/google.vue +11 -6
- package/mixins/__tests__/auth-config.test.ts +4 -6
- package/mixins/__tests__/chart.test.ts +139 -1
- package/mixins/auth-config.js +33 -10
- package/mixins/chart.js +58 -18
- package/models/__tests__/namespace.test.ts +69 -0
- package/models/apps.statefulset.js +8 -10
- package/models/chart.js +5 -1
- package/models/fleet-application.js +16 -46
- package/models/fleet.cattle.io.bundle.js +1 -38
- package/models/fleet.cattle.io.gitrepo.js +4 -0
- package/models/fleet.cattle.io.helmop.js +4 -0
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/management.cattle.io.project.js +12 -0
- package/models/namespace.js +30 -0
- package/models/workload.js +4 -1
- package/package.json +10 -10
- package/pages/auth/login.vue +8 -3
- package/pages/auth/logout.vue +6 -5
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
- package/pages/c/_cluster/apps/charts/chart.vue +29 -20
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/c/_cluster/apps/charts/install.vue +6 -5
- package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
- package/pages/c/_cluster/explorer/tools/index.vue +145 -254
- package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
- package/pages/c/_cluster/uiplugins/index.vue +221 -363
- package/pages/home.vue +1 -9
- package/plugins/axios.js +3 -2
- package/plugins/dashboard-store/resource-class.js +49 -0
- package/plugins/ember-cookie.js +7 -3
- package/plugins/steve/subscribe.js +4 -2
- package/public/index.html +2 -1
- package/rancher-components/Card/Card.vue +1 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Form/Radio/RadioButton.vue +1 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
- package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
- package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
- package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
- package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
- package/rancher-components/Pill/RcTag/index.ts +1 -0
- package/rancher-components/Pill/RcTag/types.ts +9 -0
- package/rancher-components/Pill/types.ts +1 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
- package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
- package/scripts/test-plugins-build.sh +0 -1
- package/store/__tests__/catalog.test.ts +63 -0
- package/store/__tests__/cookies.test.ts +72 -0
- package/store/auth.js +33 -10
- package/store/catalog.js +2 -2
- package/store/cookies.ts +30 -0
- package/store/prefs.js +10 -5
- package/store/type-map.js +3 -15
- package/types/extension-manager.ts +26 -0
- package/types/shell/index.d.ts +123 -27
- package/utils/__tests__/product.test.ts +129 -0
- package/utils/__tests__/resource.test.ts +87 -0
- package/utils/alertmanagerconfig.js +2 -2
- package/utils/auth.js +4 -77
- package/utils/product.ts +39 -0
- package/utils/resource.ts +35 -0
- package/utils/select.js +0 -24
- package/utils/validators/formRules/__tests__/index.test.ts +3 -0
- package/utils/validators/formRules/index.ts +2 -1
- package/vue.config.js +1 -1
- package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
- package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
- package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
- package/utils/cookie-universal.js +0 -10
- /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
|
@@ -22,12 +22,69 @@ export default {
|
|
|
22
22
|
window.location.href = idpRedirectUrl;
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
|
+
|
|
26
|
+
computed: {
|
|
27
|
+
// If any of the 3 params is specified, this is a CLI login
|
|
28
|
+
isCLILogin() {
|
|
29
|
+
const {
|
|
30
|
+
cli,
|
|
31
|
+
requestId,
|
|
32
|
+
publicKey,
|
|
33
|
+
responseType
|
|
34
|
+
} = this.$route.query;
|
|
35
|
+
|
|
36
|
+
return cli || publicKey || responseType || requestId;
|
|
37
|
+
},
|
|
38
|
+
// If this is a CLI login, we must have the correct respone type and the other params must not be empty
|
|
39
|
+
invalidCLILogin() {
|
|
40
|
+
const { requestId, publicKey, responseType } = this.$route.query;
|
|
41
|
+
|
|
42
|
+
if (this.isCLILogin) {
|
|
43
|
+
return responseType !== 'kubeconfig' || !requestId || !publicKey;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
},
|
|
48
|
+
cliLoginCode() {
|
|
49
|
+
const { requestId } = this.$route.query;
|
|
50
|
+
|
|
51
|
+
return requestId;
|
|
52
|
+
},
|
|
53
|
+
warningMessageKey() {
|
|
54
|
+
const { cli } = this.$route.query;
|
|
55
|
+
|
|
56
|
+
return cli === 'true' ? 'login.cli.warning' : 'login.cli.warningLegacy';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
25
59
|
};
|
|
26
60
|
</script>
|
|
27
61
|
|
|
28
62
|
<template>
|
|
29
63
|
<div class="text-center">
|
|
64
|
+
<div
|
|
65
|
+
v-if="isCLILogin"
|
|
66
|
+
class="cli-login"
|
|
67
|
+
>
|
|
68
|
+
<div class="cli-message">
|
|
69
|
+
{{ t('login.cli.welcome') }}
|
|
70
|
+
</div>
|
|
71
|
+
<div
|
|
72
|
+
v-if="invalidCLILogin"
|
|
73
|
+
class="cli-message cli-error"
|
|
74
|
+
>
|
|
75
|
+
{{ t('login.cli.invalidParams') }}
|
|
76
|
+
</div>
|
|
77
|
+
<template v-else>
|
|
78
|
+
<div class="cli-message">
|
|
79
|
+
{{ t(warningMessageKey, {}, true) }}
|
|
80
|
+
</div>
|
|
81
|
+
<div class="cli-login-code">
|
|
82
|
+
{{ cliLoginCode }}
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
85
|
+
</div>
|
|
30
86
|
<button
|
|
87
|
+
v-if="!invalidCLILogin"
|
|
31
88
|
ref="btn"
|
|
32
89
|
class="btn bg-primary"
|
|
33
90
|
style="font-size: 18px;"
|
|
@@ -37,3 +94,32 @@ export default {
|
|
|
37
94
|
</button>
|
|
38
95
|
</div>
|
|
39
96
|
</template>
|
|
97
|
+
<style lang="scss" scoped>
|
|
98
|
+
.cli-login {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-direction: column;
|
|
101
|
+
align-items: center;
|
|
102
|
+
|
|
103
|
+
> div {
|
|
104
|
+
margin-bottom: 8px
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.cli-message {
|
|
108
|
+
font-size: 16px;
|
|
109
|
+
|
|
110
|
+
&.cli-error {
|
|
111
|
+
color: var(--error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.cli-login-code {
|
|
116
|
+
font-family: 'Courier New', Courier, monospace;
|
|
117
|
+
border: 1px solid var(--border);
|
|
118
|
+
border-radius: var(--border-radius);
|
|
119
|
+
padding: 4px 8px;
|
|
120
|
+
margin: 8px 0 16px 0;
|
|
121
|
+
letter-spacing: 1px;
|
|
122
|
+
font-size: 16px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
</style>
|
|
@@ -4,7 +4,7 @@ import LabeledFormElement from '@shell/mixins/labeled-form-element';
|
|
|
4
4
|
import { get } from '@shell/utils/object';
|
|
5
5
|
import { LabeledTooltip } from '@components/LabeledTooltip';
|
|
6
6
|
import VueSelectOverrides from '@shell/mixins/vue-select-overrides';
|
|
7
|
-
import {
|
|
7
|
+
import { calculatePosition } from '@shell/utils/select';
|
|
8
8
|
import { generateRandomAlphaString } from '@shell/utils/string';
|
|
9
9
|
import LabeledSelectPagination from '@shell/components/form/labeled-select-utils/labeled-select-pagination';
|
|
10
10
|
import { LABEL_SELECT_NOT_OPTION_KINDS } from '@shell/types/components/labeledSelect';
|
|
@@ -169,14 +169,19 @@ export default {
|
|
|
169
169
|
},
|
|
170
170
|
|
|
171
171
|
methods: {
|
|
172
|
-
|
|
173
|
-
clickSelect() {
|
|
172
|
+
clickSelect(event) {
|
|
174
173
|
if (this.mode === _VIEW || this.loading === true || this.disabled === true) {
|
|
175
174
|
return;
|
|
176
175
|
}
|
|
177
176
|
|
|
177
|
+
// Ensure we don't toggle when clicking the clear button on multi-select
|
|
178
|
+
if (this.$attrs.multiple && event?.target.className === 'vs__deselect') {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
178
182
|
this.isOpen = !this.isOpen;
|
|
179
183
|
|
|
184
|
+
// Ensure we only focus on open, otherwise we re-open on close
|
|
180
185
|
if (this.isOpen) {
|
|
181
186
|
this.focusSearch();
|
|
182
187
|
}
|
|
@@ -262,10 +267,6 @@ export default {
|
|
|
262
267
|
|
|
263
268
|
get,
|
|
264
269
|
|
|
265
|
-
onClickOption(option, event) {
|
|
266
|
-
onClickOption.call(this, option, event);
|
|
267
|
-
},
|
|
268
|
-
|
|
269
270
|
dropdownShouldOpen(instance, forceOpen = false) {
|
|
270
271
|
if (!this.isOpen) {
|
|
271
272
|
return false;
|
|
@@ -428,7 +429,6 @@ export default {
|
|
|
428
429
|
v-else
|
|
429
430
|
class="vs__option-kind"
|
|
430
431
|
:class="{ 'has-icon' : hasGroupIcon}"
|
|
431
|
-
@mousedown="(e) => onClickOption(option, e)"
|
|
432
432
|
>
|
|
433
433
|
{{ getOptionLabel(option) }}
|
|
434
434
|
<i
|
|
@@ -286,6 +286,7 @@ export default {
|
|
|
286
286
|
<template v-slot:body>
|
|
287
287
|
<RadioGroup
|
|
288
288
|
v-model:value="value.permissionGroup"
|
|
289
|
+
:mode="mode"
|
|
289
290
|
data-testid="permission-options"
|
|
290
291
|
:options="options"
|
|
291
292
|
name="permission-group"
|
|
@@ -301,6 +302,7 @@ export default {
|
|
|
301
302
|
>
|
|
302
303
|
<Checkbox
|
|
303
304
|
v-model:value="permission.value"
|
|
305
|
+
:mode="mode"
|
|
304
306
|
:data-testid="`custom-permission-${i}`"
|
|
305
307
|
:disabled="permission.locked"
|
|
306
308
|
class="mb-5"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { randomStr } from '@shell/utils/string';
|
|
2
|
+
import { sum } from 'lodash';
|
|
3
|
+
import { computed, inject, provide, ref } from 'vue';
|
|
4
|
+
|
|
5
|
+
const UPDATE_COUNT_PROVIDER_KEY = 'update-count';
|
|
6
|
+
const USE_COUNTS_KEY = 'is-inside-resource-tabs';
|
|
7
|
+
|
|
8
|
+
type UpdateCountFn = (key: string, count: number | undefined) => void;
|
|
9
|
+
|
|
10
|
+
export const useIndicateUseCounts = () => {
|
|
11
|
+
provide(USE_COUNTS_KEY, true);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const useTabCountWatcher = () => {
|
|
15
|
+
if (!inject<boolean>(USE_COUNTS_KEY, false)) {
|
|
16
|
+
return { isCountVisible: ref<boolean>(false) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const countLedger = ref<{ [key: string]: number | undefined }>({});
|
|
20
|
+
|
|
21
|
+
const isCountVisible = computed(() => {
|
|
22
|
+
// Some tables are destroyed and recreated depending on visibility so we count keys
|
|
23
|
+
// to check if a table has been present in the tab even if the count has been cleared
|
|
24
|
+
return Object.keys(countLedger.value).length > 0;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const count = computed(() => {
|
|
28
|
+
return sum(Object.values(countLedger.value).map((count) => count || 0));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const updateCount = (key: string, count: number | undefined) => {
|
|
32
|
+
countLedger.value[key] = count;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
provide(UPDATE_COUNT_PROVIDER_KEY, updateCount);
|
|
36
|
+
|
|
37
|
+
return { isCountVisible, count };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const useTabCountUpdater = () => {
|
|
41
|
+
const tabKey = randomStr();
|
|
42
|
+
const updateCount = inject<UpdateCountFn>(UPDATE_COUNT_PROVIDER_KEY);
|
|
43
|
+
|
|
44
|
+
const updateTabCount = (count: number | undefined) => {
|
|
45
|
+
updateCount?.(tabKey, count);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const clearTabCount = () => updateTabCount(undefined);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
updateTabCount,
|
|
52
|
+
clearTabCount
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -16,6 +16,7 @@ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
|
16
16
|
import { MESSAGE, REASON } from '@shell/config/table-headers';
|
|
17
17
|
import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
|
|
18
18
|
import { headerFromSchemaColString } from '@shell/store/type-map.utils';
|
|
19
|
+
import { useIndicateUseCounts } from '@shell/components/form/ResourceTabs/composable';
|
|
19
20
|
|
|
20
21
|
export default {
|
|
21
22
|
|
|
@@ -75,6 +76,12 @@ export default {
|
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
78
|
|
|
79
|
+
setup(props) {
|
|
80
|
+
if (props.mode === _VIEW) {
|
|
81
|
+
useIndicateUseCounts();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
78
85
|
data() {
|
|
79
86
|
const inStore = this.$store.getters['currentStore'](EVENT);
|
|
80
87
|
const eventSchema = this.$store.getters[`${ inStore }/schemaFor`](EVENT); // @TODO be smarter about which resources actually ever have events
|
|
@@ -155,15 +162,13 @@ export default {
|
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
return false;
|
|
165
|
+
},
|
|
166
|
+
children() {
|
|
167
|
+
return this.$slots?.default?.() || [];
|
|
158
168
|
}
|
|
159
169
|
},
|
|
160
170
|
|
|
161
171
|
methods: {
|
|
162
|
-
// Ensures we only fetch events and show the table when the events tab has been activated
|
|
163
|
-
tabChange(neu) {
|
|
164
|
-
this.selectedTab = neu?.selectedName;
|
|
165
|
-
},
|
|
166
|
-
|
|
167
172
|
/**
|
|
168
173
|
* Conditions come from a resource's `status`. They are used by both core resources like workloads as well as those from CRDs
|
|
169
174
|
* - Workloads
|
|
@@ -243,7 +248,6 @@ export default {
|
|
|
243
248
|
@changed="tabChange"
|
|
244
249
|
>
|
|
245
250
|
<slot />
|
|
246
|
-
|
|
247
251
|
<Tab
|
|
248
252
|
v-if="showConditions"
|
|
249
253
|
label-key="resourceTabs.conditions.tab"
|
|
@@ -262,7 +266,6 @@ export default {
|
|
|
262
266
|
>
|
|
263
267
|
<!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
|
|
264
268
|
<PaginatedResourceTable
|
|
265
|
-
v-if="selectedTab === 'events'"
|
|
266
269
|
:schema="eventSchema"
|
|
267
270
|
:local-filter="filterEventsLocal"
|
|
268
271
|
:api-filter="filterEventsApi"
|
|
@@ -4,7 +4,7 @@ import LabeledFormElement from '@shell/mixins/labeled-form-element';
|
|
|
4
4
|
import VueSelectOverrides from '@shell/mixins/vue-select-overrides';
|
|
5
5
|
import { generateRandomAlphaString } from '@shell/utils/string';
|
|
6
6
|
import { LabeledTooltip } from '@components/LabeledTooltip';
|
|
7
|
-
import {
|
|
7
|
+
import { calculatePosition } from '@shell/utils/select';
|
|
8
8
|
import { _VIEW } from '@shell/config/query-params';
|
|
9
9
|
import { useClickOutside } from '@shell/composables/useClickOutside';
|
|
10
10
|
import { ref } from 'vue';
|
|
@@ -94,7 +94,11 @@ export default {
|
|
|
94
94
|
isLangSelect: {
|
|
95
95
|
type: Boolean,
|
|
96
96
|
default: false
|
|
97
|
-
}
|
|
97
|
+
},
|
|
98
|
+
loading: {
|
|
99
|
+
default: false,
|
|
100
|
+
type: Boolean
|
|
101
|
+
},
|
|
98
102
|
},
|
|
99
103
|
setup() {
|
|
100
104
|
const select = ref(null);
|
|
@@ -134,14 +138,19 @@ export default {
|
|
|
134
138
|
calculatePosition(dropdownList, component, width, this.placement);
|
|
135
139
|
},
|
|
136
140
|
|
|
137
|
-
// Ensure we only focus on open, otherwise we re-open on close
|
|
138
141
|
clickSelect(ev) {
|
|
139
142
|
if (this.mode === _VIEW || this.loading === true || this.disabled === true) {
|
|
140
143
|
return;
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
// Ensure we don't toggle when clicking the clear button on multi-select
|
|
147
|
+
if (this.$attrs.multiple && ev?.target.className === 'vs__deselect') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
143
151
|
this.isOpen = !this.isOpen;
|
|
144
152
|
|
|
153
|
+
// Ensure we only focus on open, otherwise we re-open on close
|
|
145
154
|
if (this.isOpen) {
|
|
146
155
|
this.focusSearch(ev);
|
|
147
156
|
}
|
|
@@ -163,9 +172,6 @@ export default {
|
|
|
163
172
|
|
|
164
173
|
get,
|
|
165
174
|
|
|
166
|
-
onClickOption(option, event) {
|
|
167
|
-
onClickOption.call(this, option, event);
|
|
168
|
-
},
|
|
169
175
|
selectable(opt) {
|
|
170
176
|
// Lets you disable options that are used
|
|
171
177
|
// for headings on groups of options.
|
|
@@ -352,10 +358,7 @@ export default {
|
|
|
352
358
|
<template
|
|
353
359
|
#option="option"
|
|
354
360
|
>
|
|
355
|
-
<div
|
|
356
|
-
:lang="isLangSelect ? option.value : undefined"
|
|
357
|
-
@mousedown="(e) => onClickOption(option, e)"
|
|
358
|
-
>
|
|
361
|
+
<div :lang="isLangSelect ? option.value : undefined">
|
|
359
362
|
{{ getOptionLabel(option.label) }}
|
|
360
363
|
</div>
|
|
361
364
|
</template>
|
|
@@ -291,4 +291,137 @@ describe('component: LabeledSelect', () => {
|
|
|
291
291
|
expect(spyFocus).toHaveBeenCalled();
|
|
292
292
|
expect(spyPreventDefault).not.toHaveBeenCalled();
|
|
293
293
|
});
|
|
294
|
+
|
|
295
|
+
describe('function: clickSelect', () => {
|
|
296
|
+
it('should open dropdown when clickSelect is called and not disabled', async() => {
|
|
297
|
+
const label = 'Foo';
|
|
298
|
+
const value = 'foo';
|
|
299
|
+
const wrapper = mount(LabeledSelect, {
|
|
300
|
+
props: {
|
|
301
|
+
value,
|
|
302
|
+
options: [{ label, value }],
|
|
303
|
+
disabled: false,
|
|
304
|
+
loading: false,
|
|
305
|
+
mode: _EDIT
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
310
|
+
|
|
311
|
+
wrapper.vm.clickSelect();
|
|
312
|
+
await wrapper.vm.$nextTick();
|
|
313
|
+
|
|
314
|
+
expect(wrapper.vm.isOpen).toBe(true);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should not open dropdown when clickSelect is called and disabled', async() => {
|
|
318
|
+
const label = 'Foo';
|
|
319
|
+
const value = 'foo';
|
|
320
|
+
const wrapper = mount(LabeledSelect, {
|
|
321
|
+
props: {
|
|
322
|
+
value,
|
|
323
|
+
options: [{ label, value }],
|
|
324
|
+
disabled: true,
|
|
325
|
+
loading: false,
|
|
326
|
+
mode: _EDIT
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
331
|
+
|
|
332
|
+
wrapper.vm.clickSelect();
|
|
333
|
+
await wrapper.vm.$nextTick();
|
|
334
|
+
|
|
335
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should not open dropdown when loading is true', async() => {
|
|
339
|
+
const label = 'Foo';
|
|
340
|
+
const value = 'foo';
|
|
341
|
+
const wrapper = mount(LabeledSelect, {
|
|
342
|
+
props: {
|
|
343
|
+
value,
|
|
344
|
+
options: [{ label, value }],
|
|
345
|
+
disabled: false,
|
|
346
|
+
loading: true,
|
|
347
|
+
mode: _EDIT
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
352
|
+
|
|
353
|
+
wrapper.vm.clickSelect();
|
|
354
|
+
await wrapper.vm.$nextTick();
|
|
355
|
+
|
|
356
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should not open dropdown when mode is _VIEW', async() => {
|
|
360
|
+
const label = 'Foo';
|
|
361
|
+
const value = 'foo';
|
|
362
|
+
const wrapper = mount(LabeledSelect, {
|
|
363
|
+
props: {
|
|
364
|
+
value,
|
|
365
|
+
options: [{ label, value }],
|
|
366
|
+
disabled: false,
|
|
367
|
+
loading: false,
|
|
368
|
+
mode: _VIEW
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
373
|
+
|
|
374
|
+
wrapper.vm.clickSelect();
|
|
375
|
+
await wrapper.vm.$nextTick();
|
|
376
|
+
|
|
377
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should not clear value if disabled', async() => {
|
|
381
|
+
const label = 'Foo';
|
|
382
|
+
const value = 'foo';
|
|
383
|
+
const wrapper = mount(LabeledSelect, {
|
|
384
|
+
props: {
|
|
385
|
+
value,
|
|
386
|
+
options: [{ label, value }],
|
|
387
|
+
multiple: true,
|
|
388
|
+
disabled: true,
|
|
389
|
+
mode: _EDIT
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const clearBtn = wrapper.find('.vs__deselect');
|
|
394
|
+
|
|
395
|
+
expect(clearBtn.exists()).toBe(true);
|
|
396
|
+
|
|
397
|
+
await clearBtn.trigger('mousedown');
|
|
398
|
+
await wrapper.vm.$nextTick();
|
|
399
|
+
|
|
400
|
+
expect(wrapper.emitted('update:value')).toBeUndefined();
|
|
401
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should not open dropdown when remove button is clicked', async() => {
|
|
405
|
+
const label = 'Foo';
|
|
406
|
+
const value = 'foo';
|
|
407
|
+
const wrapper = mount(LabeledSelect, {
|
|
408
|
+
props: {
|
|
409
|
+
value,
|
|
410
|
+
options: [{ label, value }],
|
|
411
|
+
multiple: true,
|
|
412
|
+
mode: _EDIT
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
417
|
+
|
|
418
|
+
const clearBtn = wrapper.find('.vs__deselect');
|
|
419
|
+
|
|
420
|
+
await clearBtn.trigger('mousedown');
|
|
421
|
+
await wrapper.vm.$nextTick();
|
|
422
|
+
|
|
423
|
+
expect(wrapper.emitted('update:value')).toBeUndefined();
|
|
424
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
294
427
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { shallowMount, mount } from '@vue/test-utils';
|
|
2
2
|
import { defineComponent } from 'vue';
|
|
3
3
|
import Select from '@shell/components/form/Select.vue';
|
|
4
|
+
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
4
5
|
|
|
5
6
|
const SelectComponent = Select as ReturnType<typeof defineComponent>;
|
|
6
7
|
|
|
@@ -100,4 +101,137 @@ describe('select.vue', () => {
|
|
|
100
101
|
expect(spyFocus).toHaveBeenCalled();
|
|
101
102
|
expect(spyPreventDefault).not.toHaveBeenCalled();
|
|
102
103
|
});
|
|
104
|
+
|
|
105
|
+
describe('function: clickSelect', () => {
|
|
106
|
+
it('should open dropdown when clickSelect is called and not disabled', async() => {
|
|
107
|
+
const label = 'Foo';
|
|
108
|
+
const value = 'foo';
|
|
109
|
+
const wrapper = mount(Select, {
|
|
110
|
+
props: {
|
|
111
|
+
value,
|
|
112
|
+
options: [{ label, value }],
|
|
113
|
+
disabled: false,
|
|
114
|
+
loading: false,
|
|
115
|
+
mode: _EDIT
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
120
|
+
|
|
121
|
+
wrapper.vm.clickSelect();
|
|
122
|
+
await wrapper.vm.$nextTick();
|
|
123
|
+
|
|
124
|
+
expect(wrapper.vm.isOpen).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should not open dropdown when clickSelect is called and disabled', async() => {
|
|
128
|
+
const label = 'Foo';
|
|
129
|
+
const value = 'foo';
|
|
130
|
+
const wrapper = mount(Select, {
|
|
131
|
+
props: {
|
|
132
|
+
value,
|
|
133
|
+
options: [{ label, value }],
|
|
134
|
+
disabled: true,
|
|
135
|
+
loading: false,
|
|
136
|
+
mode: _EDIT
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
141
|
+
|
|
142
|
+
wrapper.vm.clickSelect();
|
|
143
|
+
await wrapper.vm.$nextTick();
|
|
144
|
+
|
|
145
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should not open dropdown when loading is true', async() => {
|
|
149
|
+
const label = 'Foo';
|
|
150
|
+
const value = 'foo';
|
|
151
|
+
const wrapper = mount(Select, {
|
|
152
|
+
props: {
|
|
153
|
+
value,
|
|
154
|
+
options: [{ label, value }],
|
|
155
|
+
disabled: false,
|
|
156
|
+
loading: true,
|
|
157
|
+
mode: _EDIT
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
162
|
+
|
|
163
|
+
wrapper.vm.clickSelect();
|
|
164
|
+
await wrapper.vm.$nextTick();
|
|
165
|
+
|
|
166
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should not open dropdown when mode is _VIEW', async() => {
|
|
170
|
+
const label = 'Foo';
|
|
171
|
+
const value = 'foo';
|
|
172
|
+
const wrapper = mount(Select, {
|
|
173
|
+
props: {
|
|
174
|
+
value,
|
|
175
|
+
options: [{ label, value }],
|
|
176
|
+
disabled: false,
|
|
177
|
+
loading: false,
|
|
178
|
+
mode: _VIEW
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
183
|
+
|
|
184
|
+
wrapper.vm.clickSelect();
|
|
185
|
+
await wrapper.vm.$nextTick();
|
|
186
|
+
|
|
187
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should not clear value if disabled', async() => {
|
|
191
|
+
const label = 'Foo';
|
|
192
|
+
const value = 'foo';
|
|
193
|
+
const wrapper = mount(Select, {
|
|
194
|
+
props: {
|
|
195
|
+
value,
|
|
196
|
+
options: [{ label, value }],
|
|
197
|
+
multiple: true,
|
|
198
|
+
disabled: true,
|
|
199
|
+
mode: _EDIT
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const clearBtn = wrapper.find('.vs__deselect');
|
|
204
|
+
|
|
205
|
+
expect(clearBtn.exists()).toBe(true);
|
|
206
|
+
|
|
207
|
+
await clearBtn.trigger('mousedown');
|
|
208
|
+
await wrapper.vm.$nextTick();
|
|
209
|
+
|
|
210
|
+
expect(wrapper.emitted('update:value')).toBeUndefined();
|
|
211
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should not open dropdown when remove button is clicked', async() => {
|
|
215
|
+
const label = 'Foo';
|
|
216
|
+
const value = 'foo';
|
|
217
|
+
const wrapper = mount(Select, {
|
|
218
|
+
props: {
|
|
219
|
+
value,
|
|
220
|
+
options: [{ label, value }],
|
|
221
|
+
multiple: true,
|
|
222
|
+
mode: _EDIT
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
227
|
+
|
|
228
|
+
const clearBtn = wrapper.find('.vs__deselect');
|
|
229
|
+
|
|
230
|
+
await clearBtn.trigger('mousedown');
|
|
231
|
+
await wrapper.vm.$nextTick();
|
|
232
|
+
|
|
233
|
+
expect(wrapper.emitted('update:value')).toBeUndefined();
|
|
234
|
+
expect(wrapper.vm.isOpen).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
103
237
|
});
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
RcDropdownSeparator,
|
|
30
30
|
RcDropdownTrigger
|
|
31
31
|
} from '@components/RcDropdown';
|
|
32
|
+
import { SLO_AUTH_PROVIDERS } from '@shell/store/auth';
|
|
32
33
|
|
|
33
34
|
export default {
|
|
34
35
|
|
|
@@ -99,10 +100,10 @@ export default {
|
|
|
99
100
|
'showWorkspaceSwitcher'
|
|
100
101
|
]),
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
sloAuthProviderEnabled() {
|
|
103
104
|
const publicAuthProviders = this.$store.getters['rancher/all']('authProvider');
|
|
104
105
|
|
|
105
|
-
return publicAuthProviders.find((authProvider) => configType[authProvider
|
|
106
|
+
return publicAuthProviders.find((authProvider) => SLO_AUTH_PROVIDERS.includes(configType[authProvider?.id])) || {};
|
|
106
107
|
},
|
|
107
108
|
|
|
108
109
|
shouldShowSloLogoutModal() {
|
|
@@ -111,7 +112,7 @@ export default {
|
|
|
111
112
|
return false;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.
|
|
115
|
+
const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.sloAuthProviderEnabled;
|
|
115
116
|
|
|
116
117
|
return logoutAllSupported && logoutAllEnabled && !logoutAllForced;
|
|
117
118
|
},
|
|
@@ -276,8 +277,8 @@ export default {
|
|
|
276
277
|
showSloModal() {
|
|
277
278
|
this.$store.dispatch('management/promptModal', {
|
|
278
279
|
component: 'SloDialog',
|
|
279
|
-
componentProps: { authProvider: this.
|
|
280
|
-
modalWidth: '
|
|
280
|
+
componentProps: { authProvider: this.sloAuthProviderEnabled },
|
|
281
|
+
modalWidth: '600px'
|
|
281
282
|
});
|
|
282
283
|
},
|
|
283
284
|
// Sizes the product area of the header such that it shrinks to ensure the whole header bar can be shown
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ExtensionManager } from '@shell/types/extension-manager';
|
|
2
|
+
import { getExtensionManager } from '@shell/core/extension-manager-impl';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Provides access to the registered extension manager instance. Used within Vue
|
|
6
|
+
* components or other composables that require extension functionality.
|
|
7
|
+
* @returns The extension manager instance
|
|
8
|
+
*/
|
|
9
|
+
export const useExtensionManager = (): ExtensionManager => {
|
|
10
|
+
const extension = getExtensionManager();
|
|
11
|
+
|
|
12
|
+
if (!extension) {
|
|
13
|
+
throw new Error('useExtensionManager must be called after the extensionManager has been initialized');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return extension;
|
|
17
|
+
};
|