@rancher/shell 3.0.8-rc.9 → 3.0.8
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/apis/impl/apis.ts +61 -0
- package/apis/index.ts +40 -0
- package/apis/intf/modal.ts +90 -0
- package/apis/intf/shell.ts +36 -0
- package/apis/intf/slide-in.ts +98 -0
- package/apis/intf/system.ts +41 -0
- package/apis/shell/__tests__/modal.test.ts +80 -0
- package/apis/shell/__tests__/notifications.test.ts +71 -0
- package/apis/shell/__tests__/slide-in.test.ts +54 -0
- package/apis/shell/__tests__/system.test.ts +129 -0
- package/apis/shell/index.ts +38 -0
- package/apis/shell/modal.ts +41 -0
- package/apis/shell/notifications.ts +65 -0
- package/apis/shell/slide-in.ts +33 -0
- package/apis/shell/system.ts +65 -0
- package/apis/vue-shim.d.ts +11 -0
- package/assets/styles/global/_tooltip.scss +6 -1
- package/assets/translations/en-us.yaml +5 -0
- package/components/ActionMenuShell.vue +3 -1
- package/components/CruResource.vue +8 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
- package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
- package/components/LocaleSelector.vue +2 -2
- package/components/ModalManager.vue +11 -1
- package/components/Questions/__tests__/Yaml.test.ts +1 -1
- package/components/RelatedResources.vue +5 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
- package/components/ResourceDetail/Masthead/latest.vue +23 -21
- package/components/ResourceDetail/index.vue +3 -0
- package/components/ResourceTable.vue +54 -21
- package/components/SlideInPanelManager.vue +16 -11
- package/components/SortableTable/THead.vue +2 -1
- package/components/SortableTable/index.vue +20 -2
- package/components/Tabbed/index.vue +37 -2
- package/components/__tests__/NamespaceFilter.test.ts +49 -0
- package/components/auth/SelectPrincipal.vue +4 -0
- package/components/auth/login/ldap.vue +3 -3
- package/components/fleet/FleetSecretSelector.vue +1 -1
- package/components/form/KeyValue.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -1
- package/components/form/NodeScheduling.vue +2 -2
- package/components/form/ResourceTabs/composable.ts +2 -2
- package/components/form/ResourceTabs/index.vue +0 -2
- package/components/form/__tests__/NameNsDescription.test.ts +42 -0
- package/components/formatter/LinkName.vue +5 -0
- package/components/nav/Group.vue +25 -7
- package/components/nav/Header.vue +1 -1
- package/components/nav/NamespaceFilter.vue +1 -0
- package/components/nav/Type.vue +17 -6
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
- package/components/nav/__tests__/Type.test.ts +59 -0
- package/composables/cruResource.ts +27 -0
- package/composables/focusTrap.ts +3 -1
- package/composables/resourceDetail.ts +15 -0
- package/composables/useLabeledFormElement.ts +3 -4
- package/config/product/fleet.js +1 -1
- package/config/router/navigation-guards/clusters.js +3 -3
- package/config/router/navigation-guards/products.js +1 -1
- package/config/router/routes.js +1 -5
- package/core/__tests__/extension-manager-impl.test.js +437 -0
- package/core/extension-manager-impl.js +6 -27
- package/core/plugin-helpers.ts +2 -2
- package/core/plugin.ts +9 -1
- package/core/plugins-loader.js +2 -2
- package/core/types-provisioning.ts +4 -0
- package/core/types.ts +35 -0
- package/detail/provisioning.cattle.io.cluster.vue +8 -6
- package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
- package/dialog/MoveNamespaceDialog.vue +20 -4
- package/dialog/SearchDialog.vue +1 -0
- package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
- package/directives/__tests__/clean-tooltip.test.ts +298 -0
- package/directives/clean-tooltip.ts +234 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
- package/edit/fleet.cattle.io.helmop.vue +5 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
- package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
- package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
- package/edit/resources.cattle.io.restore.vue +1 -1
- package/edit/workload/Job.vue +2 -2
- package/edit/workload/index.vue +1 -1
- package/initialize/install-plugins.js +4 -5
- package/machine-config/azure.vue +1 -1
- package/machine-config/components/GCEImage.vue +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
- package/models/chart.js +70 -74
- package/models/management.cattle.io.cluster.js +1 -1
- package/models/provisioning.cattle.io.cluster.js +11 -3
- package/package.json +7 -7
- package/pages/auth/login.vue +3 -3
- package/pages/auth/setup.vue +1 -1
- package/pages/auth/verify.vue +3 -3
- package/pages/c/_cluster/apps/charts/index.vue +122 -24
- package/pages/c/_cluster/apps/charts/install.vue +33 -0
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
- package/pages/c/_cluster/fleet/index.vue +4 -7
- package/pages/c/_cluster/settings/index.vue +5 -0
- package/pkg/auto-import.js +3 -3
- package/pkg/dynamic-importer.lib.js +1 -1
- package/pkg/import.js +1 -1
- package/plugins/__tests__/mutations.tests.ts +179 -0
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/model-loader.js +1 -1
- package/plugins/dashboard-store/mutations.js +23 -2
- package/plugins/dashboard-store/resource-class.js +8 -3
- package/plugins/plugin.js +2 -2
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
- package/plugins/steve/steve-class.js +1 -1
- package/plugins/steve/steve-pagination-utils.ts +108 -43
- package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
- package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
- package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
- package/scripts/publish-shell.sh +25 -0
- package/store/__tests__/catalog.test.ts +1 -1
- package/store/__tests__/type-map.test.ts +164 -2
- package/store/auth.js +23 -11
- package/store/i18n.js +3 -3
- package/store/index.js +5 -3
- package/store/notifications.ts +2 -0
- package/store/prefs.js +2 -2
- package/store/type-map.js +17 -7
- package/types/internal-api/shell/modal.d.ts +6 -6
- package/types/notifications/index.ts +126 -15
- package/types/rancher/index.d.ts +9 -0
- package/types/shell/index.d.ts +16 -1
- package/types/vue-shim.d.ts +5 -4
- package/utils/__tests__/router.test.js +238 -0
- package/utils/cluster.js +4 -1
- package/utils/fleet.ts +8 -1
- package/utils/pagination-utils.ts +2 -2
- package/utils/pagination-wrapper.ts +1 -1
- package/utils/router.js +50 -0
- package/utils/unit-tests/pagination-utils.spec.ts +8 -8
- package/vue.config.js +3 -3
- package/composables/useExtensionManager.ts +0 -17
- package/core/__test__/extension-manager-impl.test.js +0 -236
- package/core/plugins.js +0 -38
- package/directives/clean-tooltip.js +0 -32
- package/plugins/internal-api/index.ts +0 -37
- package/plugins/internal-api/shared/base-api.ts +0 -13
- package/plugins/internal-api/shell/shell.api.ts +0 -108
- package/types/internal-api/shell/growl.d.ts +0 -25
- package/types/internal-api/shell/slideIn.d.ts +0 -15
|
@@ -31,6 +31,10 @@ const fetch = useFetch(async() => {
|
|
|
31
31
|
return r;
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
const stateBackground = computed(() => {
|
|
35
|
+
return fetch.value.data?.stateSimpleColor || 'unknown';
|
|
36
|
+
});
|
|
37
|
+
|
|
34
38
|
const resourceTypeLabel = computed(() => {
|
|
35
39
|
if (!fetch.value.data) {
|
|
36
40
|
return '';
|
|
@@ -67,7 +71,7 @@ const actionInvoked = () => {
|
|
|
67
71
|
>
|
|
68
72
|
<RcStatusIndicator
|
|
69
73
|
shape="disc"
|
|
70
|
-
:status="
|
|
74
|
+
:status="stateBackground"
|
|
71
75
|
/>
|
|
72
76
|
<router-link
|
|
73
77
|
:to="props.detailLocation || fetch.data.detailLocation || '#'"
|
|
@@ -42,27 +42,29 @@ const store = useStore();
|
|
|
42
42
|
</script>
|
|
43
43
|
|
|
44
44
|
<template>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
45
|
+
<div>
|
|
46
|
+
<TitleBar v-bind="titleBarProps" />
|
|
47
|
+
<Banner
|
|
48
|
+
v-if="bannerProps"
|
|
49
|
+
v-ui-context="{
|
|
50
|
+
store: store,
|
|
51
|
+
icon: 'icon-info',
|
|
52
|
+
hookable: true,
|
|
53
|
+
value: {
|
|
54
|
+
bannerProps,
|
|
55
|
+
resource: uiCtxResource
|
|
56
|
+
},
|
|
57
|
+
tag: '__details-state-banner',
|
|
58
|
+
description: 'Status Message'
|
|
59
|
+
}"
|
|
60
|
+
class="new state-banner"
|
|
61
|
+
v-bind="bannerProps"
|
|
62
|
+
/>
|
|
63
|
+
<Metadata
|
|
64
|
+
v-bind="metadataProps"
|
|
65
|
+
class="mmt-4"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
66
68
|
</template>
|
|
67
69
|
|
|
68
70
|
<style lang="scss" scoped>
|
|
@@ -14,6 +14,7 @@ import { clone, diff } from '@shell/utils/object';
|
|
|
14
14
|
import IconMessage from '@shell/components/IconMessage';
|
|
15
15
|
import { stringify } from '@shell/utils/error';
|
|
16
16
|
import { Banner } from '@components/Banner';
|
|
17
|
+
import { useResourceDetailPageProvider } from '@shell/composables/resourceDetail';
|
|
17
18
|
|
|
18
19
|
function modeFor(route) {
|
|
19
20
|
if ( route.query?.mode === _IMPORT ) {
|
|
@@ -116,6 +117,7 @@ export default {
|
|
|
116
117
|
|
|
117
118
|
if ( mode === _VIEW && hasCustomDetail && (!requested || requested === _DETAIL) ) {
|
|
118
119
|
as = _DETAIL;
|
|
120
|
+
useResourceDetailPageProvider();
|
|
119
121
|
} else if ( hasCustomEdit && (!requested || requested === _CONFIG) ) {
|
|
120
122
|
as = _CONFIG;
|
|
121
123
|
} else {
|
|
@@ -351,6 +353,7 @@ export default {
|
|
|
351
353
|
|
|
352
354
|
methods: {
|
|
353
355
|
stringify,
|
|
356
|
+
|
|
354
357
|
setSubtype(subtype) {
|
|
355
358
|
this.resourceSubtype = subtype;
|
|
356
359
|
},
|
|
@@ -6,7 +6,7 @@ import ButtonGroup from '@shell/components/ButtonGroup';
|
|
|
6
6
|
import SortableTable from '@shell/components/SortableTable';
|
|
7
7
|
import { NAMESPACE, AGE } from '@shell/config/table-headers';
|
|
8
8
|
import { findBy } from '@shell/utils/array';
|
|
9
|
-
import { ExtensionPoint, TableColumnLocation } from '@shell/core/types';
|
|
9
|
+
import { ExtensionPoint, TableColumnLocation, TableLocation } from '@shell/core/types';
|
|
10
10
|
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
11
11
|
import { ToggleSwitch } from '@components/Form/ToggleSwitch';
|
|
12
12
|
import ResourceTableWatch from '@shell/mixins/resource-table-watch';
|
|
@@ -316,29 +316,10 @@ export default {
|
|
|
316
316
|
|
|
317
317
|
// add custom table columns provided by the extensions ExtensionPoint.TABLE_COL hook
|
|
318
318
|
// gate it so that we prevent errors on older versions of dashboard
|
|
319
|
-
if (this.$store.$
|
|
319
|
+
if (this.$store.$extension?.getUIConfig) {
|
|
320
320
|
// { column: TableColumn, paginationColumn: PaginationTableColumn }[]
|
|
321
321
|
const extensionCols = getApplicableExtensionEnhancements(this, ExtensionPoint.TABLE_COL, TableColumnLocation.RESOURCE, this.$route);
|
|
322
322
|
|
|
323
|
-
// Try and insert the columns before the Age column
|
|
324
|
-
let insertPosition = headers.length;
|
|
325
|
-
|
|
326
|
-
if (headers.length > 0) {
|
|
327
|
-
const ageColIndex = headers.findIndex((h) => h.name === AGE.name);
|
|
328
|
-
|
|
329
|
-
if (ageColIndex >= 0) {
|
|
330
|
-
insertPosition = ageColIndex;
|
|
331
|
-
} else {
|
|
332
|
-
// we've found some labels with ' ', which isn't necessarily empty (explore action/button)
|
|
333
|
-
// if we are to add cols, let's push them before these so that the UI doesn't look weird
|
|
334
|
-
const lastViableColIndex = headers.findIndex((h) => (!h.label || !h.label?.trim()) && (!h.labelKey || !h.labelKey?.trim()));
|
|
335
|
-
|
|
336
|
-
if (lastViableColIndex >= 0) {
|
|
337
|
-
insertPosition = lastViableColIndex;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
323
|
// adding extension defined cols to the correct header config
|
|
343
324
|
extensionCols.forEach((config) => {
|
|
344
325
|
let { column: col, paginationColumn } = config;
|
|
@@ -377,6 +358,37 @@ export default {
|
|
|
377
358
|
if (!col.value && col.getValue) {
|
|
378
359
|
col.value = col.getValue;
|
|
379
360
|
}
|
|
361
|
+
|
|
362
|
+
// Establish a valid header position for the new table column
|
|
363
|
+
let insertPosition = headers.length;
|
|
364
|
+
|
|
365
|
+
if (headers.length > 0) {
|
|
366
|
+
const ageColIndex = headers.findIndex((h) => h.name === AGE.name);
|
|
367
|
+
|
|
368
|
+
if (ageColIndex >= 0) {
|
|
369
|
+
// we will allow for the table col to be added right after the AGE col
|
|
370
|
+
// but that will be the limit
|
|
371
|
+
insertPosition = ageColIndex + 1;
|
|
372
|
+
} else {
|
|
373
|
+
// we've found some labels with ' ', which isn't necessarily empty (explore action/button)
|
|
374
|
+
// if we are to add cols, let's push them before these so that the UI doesn't look weird
|
|
375
|
+
const lastViableColIndex = headers.findIndex((h) => (!h.label || !h.label?.trim()) && (!h.labelKey || !h.labelKey?.trim()));
|
|
376
|
+
|
|
377
|
+
if (lastViableColIndex >= 0) {
|
|
378
|
+
insertPosition = lastViableColIndex;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// apply table col ordering if it's present on the new table col config
|
|
384
|
+
if (col.weight) {
|
|
385
|
+
if (col.weight < 0) {
|
|
386
|
+
insertPosition = 0;
|
|
387
|
+
} else if (col.weight < insertPosition) {
|
|
388
|
+
insertPosition = col.weight;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
380
392
|
headers.splice(insertPosition, 0, col);
|
|
381
393
|
});
|
|
382
394
|
}
|
|
@@ -414,6 +426,16 @@ export default {
|
|
|
414
426
|
return headers;
|
|
415
427
|
},
|
|
416
428
|
|
|
429
|
+
_applicableExtensionTableHooks() {
|
|
430
|
+
if (this.$store.$plugin?.getUIConfig) {
|
|
431
|
+
const extensionTableHooks = getApplicableExtensionEnhancements(this, ExtensionPoint.TABLE, TableLocation.RESOURCE, this.$route);
|
|
432
|
+
|
|
433
|
+
return extensionTableHooks;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return [];
|
|
437
|
+
},
|
|
438
|
+
|
|
417
439
|
/**
|
|
418
440
|
* Take rows and filter out entries given the namespace filter
|
|
419
441
|
*/
|
|
@@ -640,6 +662,16 @@ export default {
|
|
|
640
662
|
}
|
|
641
663
|
},
|
|
642
664
|
|
|
665
|
+
// this is where we handle the callbacks to the TABLE extension hooks
|
|
666
|
+
handleSortableTableInteraction(arg) {
|
|
667
|
+
if (this._applicableExtensionTableHooks?.length) {
|
|
668
|
+
this._applicableExtensionTableHooks.forEach((item) => {
|
|
669
|
+
if (item.tableHook) {
|
|
670
|
+
item.tableHook(arg);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
}
|
|
643
675
|
}
|
|
644
676
|
};
|
|
645
677
|
</script>
|
|
@@ -679,6 +711,7 @@ export default {
|
|
|
679
711
|
@clickedActionButton="handleActionButtonClick"
|
|
680
712
|
@group-value-change="group = $event"
|
|
681
713
|
@enter="handleEnterKeyPress"
|
|
714
|
+
@sortable-table-interaction="handleSortableTableInteraction"
|
|
682
715
|
>
|
|
683
716
|
<template
|
|
684
717
|
v-if="showGrouping && _groupOptions.length > 1"
|
|
@@ -57,17 +57,22 @@ watch(
|
|
|
57
57
|
/**
|
|
58
58
|
* trigger focus trap
|
|
59
59
|
*/
|
|
60
|
-
() =>
|
|
61
|
-
(neu) => {
|
|
62
|
-
if (neu) {
|
|
63
|
-
const opts = {
|
|
60
|
+
() => isOpen?.value,
|
|
61
|
+
(neu, old) => {
|
|
62
|
+
if (neu && neu !== old) {
|
|
63
|
+
const opts:any = {
|
|
64
64
|
...DEFAULT_FOCUS_TRAP_OPTS,
|
|
65
|
+
// putting the initial focus on the first element that is not conditionally displayed
|
|
66
|
+
initialFocus: slideInPanelManagerClose.value
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const returnFocusSelector = currentProps?.value?.returnFocusSelector;
|
|
70
|
+
|
|
71
|
+
if (returnFocusSelector) {
|
|
65
72
|
/**
|
|
66
73
|
* will return focus to the first iterable node of this container select
|
|
67
74
|
*/
|
|
68
|
-
setReturnFocus
|
|
69
|
-
const returnFocusSelector = currentProps?.value?.returnFocusSelector;
|
|
70
|
-
|
|
75
|
+
opts.setReturnFocus = () => {
|
|
71
76
|
if (returnFocusSelector && !document.querySelector(returnFocusSelector)) {
|
|
72
77
|
console.warn('SlideInPanelManager: cannot find elem with "returnFocusSelector", returning focus to main view'); // eslint-disable-line no-console
|
|
73
78
|
|
|
@@ -75,10 +80,8 @@ watch(
|
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
return returnFocusSelector || '.dashboard-root';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
initialFocus: slideInPanelManagerClose.value
|
|
81
|
-
};
|
|
83
|
+
};
|
|
84
|
+
}
|
|
82
85
|
|
|
83
86
|
useWatcherBasedSetupFocusTrapWithDestroyIncluded(
|
|
84
87
|
() => {
|
|
@@ -166,6 +169,8 @@ function closePanel() {
|
|
|
166
169
|
data-testid="slide-in-close"
|
|
167
170
|
:tabindex="isOpen ? 0 : -1"
|
|
168
171
|
@click="closePanel"
|
|
172
|
+
@keypress.enter="closePanel"
|
|
173
|
+
@keyup.space="closePanel"
|
|
169
174
|
/>
|
|
170
175
|
</div>
|
|
171
176
|
<div class="main-panel">
|
|
@@ -52,7 +52,8 @@ export default {
|
|
|
52
52
|
'group-value-change',
|
|
53
53
|
'selection',
|
|
54
54
|
'rowClick',
|
|
55
|
-
'enter'
|
|
55
|
+
'enter',
|
|
56
|
+
'sortable-table-interaction',
|
|
56
57
|
],
|
|
57
58
|
|
|
58
59
|
components: {
|
|
@@ -765,7 +766,7 @@ export default {
|
|
|
765
766
|
needRef = true;
|
|
766
767
|
} else {
|
|
767
768
|
// Check if we have a formatter from a plugin
|
|
768
|
-
const pluginFormatter = this.$
|
|
769
|
+
const pluginFormatter = this.$extension?.getDynamic('formatters', c.formatter);
|
|
769
770
|
|
|
770
771
|
if (pluginFormatter) {
|
|
771
772
|
component = defineAsyncComponent(pluginFormatter);
|
|
@@ -1058,6 +1059,23 @@ export default {
|
|
|
1058
1059
|
},
|
|
1059
1060
|
|
|
1060
1061
|
paginationChanged() {
|
|
1062
|
+
// event used for extensions TABLE hooks
|
|
1063
|
+
this.$emit('sortable-table-interaction', {
|
|
1064
|
+
pagination: {
|
|
1065
|
+
page: this.page,
|
|
1066
|
+
perPage: this.perPage,
|
|
1067
|
+
},
|
|
1068
|
+
filtering: {
|
|
1069
|
+
searchFields: this.searchFields,
|
|
1070
|
+
searchQuery: this.searchQuery
|
|
1071
|
+
},
|
|
1072
|
+
sorting: {
|
|
1073
|
+
sort: this.sortFields,
|
|
1074
|
+
sortBy: this.sortBy,
|
|
1075
|
+
descending: this.descending
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1061
1079
|
if (!this.externalPaginationEnabled) {
|
|
1062
1080
|
return;
|
|
1063
1081
|
}
|
|
@@ -7,6 +7,10 @@ import findIndex from 'lodash/findIndex';
|
|
|
7
7
|
import { ExtensionPoint, TabLocation } from '@shell/core/types';
|
|
8
8
|
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
9
9
|
import Tab from '@shell/components/Tabbed/Tab';
|
|
10
|
+
import { ref } from 'vue';
|
|
11
|
+
import { useIsInResourceDetailDrawer } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
|
|
12
|
+
import { useIsInResourceDetailPage } from '@shell/composables/resourceDetail';
|
|
13
|
+
import { useIsInResourceCreatePage, useIsInResourceEditPage } from '@shell/composables/cruResource';
|
|
10
14
|
|
|
11
15
|
export default {
|
|
12
16
|
name: 'Tabbed',
|
|
@@ -105,7 +109,14 @@ export default {
|
|
|
105
109
|
},
|
|
106
110
|
|
|
107
111
|
data() {
|
|
108
|
-
const
|
|
112
|
+
const location = this.getInitialTabLocation();
|
|
113
|
+
let extensionTabs = this.showExtensionTabs ? getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, location, this.$route, this, this.extensionParams) || [] : [];
|
|
114
|
+
const legacyExtensionTabs = this.showExtensionTabs ? getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route, this, this.extensionParams) || [] : [];
|
|
115
|
+
|
|
116
|
+
if (!extensionTabs.length) {
|
|
117
|
+
// Support legacy tabs for RESOURCE_DETAIL location
|
|
118
|
+
extensionTabs = legacyExtensionTabs;
|
|
119
|
+
}
|
|
109
120
|
|
|
110
121
|
const parsedExtTabs = extensionTabs.map((item) => {
|
|
111
122
|
return {
|
|
@@ -130,7 +141,18 @@ export default {
|
|
|
130
141
|
// hide tabs based on tab count IF flag is active
|
|
131
142
|
hideTabs() {
|
|
132
143
|
return this.hideSingleTab && this.sortedTabs.length === 1;
|
|
133
|
-
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
setup() {
|
|
148
|
+
const isInResourceDetailDrawer = ref(useIsInResourceDetailDrawer());
|
|
149
|
+
const isInResourceDetailPage = ref(useIsInResourceDetailPage());
|
|
150
|
+
const isInResourceEditPage = ref(useIsInResourceEditPage());
|
|
151
|
+
const isInResourceCreatePage = ref(useIsInResourceCreatePage());
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
isInResourceDetailDrawer, isInResourceDetailPage, isInResourceEditPage, isInResourceCreatePage
|
|
155
|
+
};
|
|
134
156
|
},
|
|
135
157
|
|
|
136
158
|
watch: {
|
|
@@ -166,6 +188,19 @@ export default {
|
|
|
166
188
|
},
|
|
167
189
|
|
|
168
190
|
methods: {
|
|
191
|
+
getInitialTabLocation() {
|
|
192
|
+
if (this.isInResourceEditPage) {
|
|
193
|
+
return TabLocation.RESOURCE_EDIT_PAGE;
|
|
194
|
+
} else if (this.isInResourceDetailDrawer) {
|
|
195
|
+
return TabLocation.RESOURCE_SHOW_CONFIGURATION;
|
|
196
|
+
} else if (this.isInResourceDetailPage) {
|
|
197
|
+
return TabLocation.RESOURCE_DETAIL_PAGE;
|
|
198
|
+
} else if (this.isInResourceCreatePage) {
|
|
199
|
+
return TabLocation.RESOURCE_CREATE_PAGE;
|
|
200
|
+
} else {
|
|
201
|
+
return TabLocation.OTHER;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
169
204
|
hasIcon(tab) {
|
|
170
205
|
return tab.displayAlertIcon || (tab.error && !tab.active);
|
|
171
206
|
},
|
|
@@ -244,4 +244,53 @@ describe('component: NamespaceFilter', () => {
|
|
|
244
244
|
|
|
245
245
|
it.todo('should generate the options based on the Rancher resources');
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
describe('given filter input text selection', () => {
|
|
249
|
+
it('should allow text selection by stopping mousedown propagation', async() => {
|
|
250
|
+
const wrapper = mount(NamespaceFilter, {
|
|
251
|
+
computed: {
|
|
252
|
+
filtered: () => [],
|
|
253
|
+
options: () => [],
|
|
254
|
+
value: () => [],
|
|
255
|
+
},
|
|
256
|
+
global: {
|
|
257
|
+
mocks: {
|
|
258
|
+
t: (key: string) => key,
|
|
259
|
+
$store: { getters: { 'i18n/t': () => '', namespaceFilterMode: () => undefined } },
|
|
260
|
+
$fetchState: { pending: false }
|
|
261
|
+
},
|
|
262
|
+
directives: {
|
|
263
|
+
'clean-tooltip': () => {},
|
|
264
|
+
shortkey: () => {},
|
|
265
|
+
},
|
|
266
|
+
stubs: { RcButton: { template: '<button><slot /></button>' } },
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Open the dropdown to reveal the filter input
|
|
271
|
+
const dropdown = wrapper.find('[data-testid="namespaces-dropdown"]');
|
|
272
|
+
|
|
273
|
+
await dropdown.trigger('click');
|
|
274
|
+
|
|
275
|
+
// Find the filter input
|
|
276
|
+
const filterInput = wrapper.find('.ns-filter-input');
|
|
277
|
+
|
|
278
|
+
expect(filterInput.exists()).toBe(true);
|
|
279
|
+
|
|
280
|
+
// Trigger mousedown on the filter input and capture the event
|
|
281
|
+
const mousedownEvent = new MouseEvent('mousedown', {
|
|
282
|
+
bubbles: true,
|
|
283
|
+
cancelable: true
|
|
284
|
+
});
|
|
285
|
+
const stopPropagationSpy = jest.spyOn(mousedownEvent, 'stopPropagation');
|
|
286
|
+
|
|
287
|
+
filterInput.element.dispatchEvent(mousedownEvent);
|
|
288
|
+
|
|
289
|
+
// Verify stopPropagation was called (which allows text selection)
|
|
290
|
+
expect(stopPropagationSpy).toHaveBeenCalledWith();
|
|
291
|
+
|
|
292
|
+
// Verify the default was NOT prevented (text selection should work)
|
|
293
|
+
expect(mousedownEvent.defaultPrevented).toBe(false);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
247
296
|
});
|
|
@@ -175,6 +175,10 @@ export default {
|
|
|
175
175
|
if ( this.searchStr === str ) {
|
|
176
176
|
// If not, they've already typed something else
|
|
177
177
|
this.options = res.map((x) => x.id);
|
|
178
|
+
// display the search results if the dropdown has been closed
|
|
179
|
+
if (this.options.length) {
|
|
180
|
+
this.$refs['labeled-select'].isOpen = true;
|
|
181
|
+
}
|
|
178
182
|
}
|
|
179
183
|
} catch (e) {
|
|
180
184
|
this.options = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
|
-
import { ref, computed
|
|
2
|
+
import { ref, computed } from 'vue';
|
|
3
3
|
import { _EDIT } from '@shell/config/query-params';
|
|
4
4
|
import { TYPES } from '@shell/models/secret';
|
|
5
5
|
import { SECRET } from '@shell/config/types';
|
|
@@ -305,7 +305,7 @@ export default {
|
|
|
305
305
|
namespace.value = toRef(props.forceNamespace);
|
|
306
306
|
updateNamespace(namespace);
|
|
307
307
|
} else if (props.namespaceKey) {
|
|
308
|
-
namespace.value = get(v, props.namespaceKey);
|
|
308
|
+
namespace.value = get(v.value, props.namespaceKey);
|
|
309
309
|
} else {
|
|
310
310
|
namespace.value = metadata?.namespace;
|
|
311
311
|
}
|
|
@@ -181,7 +181,7 @@ export default {
|
|
|
181
181
|
:options="selectNodeOptions"
|
|
182
182
|
:mode="mode"
|
|
183
183
|
:data-testid="'node-scheduling-selectNode'"
|
|
184
|
-
@
|
|
184
|
+
@update:value="update"
|
|
185
185
|
/>
|
|
186
186
|
</div>
|
|
187
187
|
<template v-if="selectNode === 'nodeSelector'">
|
|
@@ -205,7 +205,7 @@ export default {
|
|
|
205
205
|
v-model:value="nodeAffinity"
|
|
206
206
|
:mode="mode"
|
|
207
207
|
:data-testid="'node-scheduling-nodeAffinity'"
|
|
208
|
-
@
|
|
208
|
+
@update:value="update"
|
|
209
209
|
/>
|
|
210
210
|
</template>
|
|
211
211
|
</div>
|
|
@@ -39,10 +39,10 @@ export const useTabCountWatcher = () => {
|
|
|
39
39
|
|
|
40
40
|
export const useTabCountUpdater = () => {
|
|
41
41
|
const tabKey = randomStr();
|
|
42
|
-
const updateCount = inject<UpdateCountFn>(UPDATE_COUNT_PROVIDER_KEY);
|
|
42
|
+
const updateCount = inject<UpdateCountFn>(UPDATE_COUNT_PROVIDER_KEY, () => { });
|
|
43
43
|
|
|
44
44
|
const updateTabCount = (count: number | undefined) => {
|
|
45
|
-
updateCount
|
|
45
|
+
updateCount(tabKey, count);
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
const clearTabCount = () => updateTabCount(undefined);
|
|
@@ -101,7 +101,6 @@ export default {
|
|
|
101
101
|
return {
|
|
102
102
|
eventSchema,
|
|
103
103
|
EVENT,
|
|
104
|
-
selectedTab: this.defaultTab,
|
|
105
104
|
inStore,
|
|
106
105
|
showConditions: false,
|
|
107
106
|
paginationHeaders
|
|
@@ -245,7 +244,6 @@ export default {
|
|
|
245
244
|
:default-tab="defaultTab"
|
|
246
245
|
:resource="value"
|
|
247
246
|
:use-hash="useHash"
|
|
248
|
-
@changed="tabChange"
|
|
249
247
|
>
|
|
250
248
|
<slot />
|
|
251
249
|
<Tab
|
|
@@ -87,6 +87,48 @@ describe('component: NameNsDescription', () => {
|
|
|
87
87
|
expect(wrapper.emitted().isNamespaceNew?.[0][0]).toBe(true);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
it('should set the namespace using the namespaceKey prop', () => {
|
|
91
|
+
const namespaceName = 'custom-namespace';
|
|
92
|
+
const store = createStore({
|
|
93
|
+
getters: {
|
|
94
|
+
allowedNamespaces: () => () => ({ [namespaceName]: true }),
|
|
95
|
+
currentStore: () => () => 'cluster',
|
|
96
|
+
'cluster/schemaFor': () => jest.fn()
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const wrapper = mount(NameNsDescription, {
|
|
101
|
+
props: {
|
|
102
|
+
value: {
|
|
103
|
+
setAnnotation: jest.fn(),
|
|
104
|
+
metadata: {},
|
|
105
|
+
value: { metadata: { namespace: namespaceName } }
|
|
106
|
+
},
|
|
107
|
+
mode: 'create',
|
|
108
|
+
namespaceKey: 'value.metadata.namespace',
|
|
109
|
+
},
|
|
110
|
+
global: {
|
|
111
|
+
provide: { store },
|
|
112
|
+
mocks: {
|
|
113
|
+
$store: {
|
|
114
|
+
dispatch: jest.fn(),
|
|
115
|
+
getters: {
|
|
116
|
+
namespaces: jest.fn(),
|
|
117
|
+
'customizations/getPreviewCluster': {
|
|
118
|
+
ready: true,
|
|
119
|
+
isLocal: false,
|
|
120
|
+
badge: {},
|
|
121
|
+
},
|
|
122
|
+
'i18n/t': jest.fn(),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect((wrapper.vm as any).namespace).toBe(namespaceName);
|
|
130
|
+
});
|
|
131
|
+
|
|
90
132
|
it('renders the name input with the expected value', () => {
|
|
91
133
|
const namespaceName = 'test';
|
|
92
134
|
const store = createStore({
|
|
@@ -41,6 +41,11 @@ export default {
|
|
|
41
41
|
product: this.product || EXPLORER,
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
// Having an undefined param can yield a console warning like [Vue Router warn]: Discarded invalid param(s) "namespace" when navigating
|
|
45
|
+
if (!params.namespace) {
|
|
46
|
+
delete params.namespace;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
return { name, params };
|
|
45
50
|
},
|
|
46
51
|
|