@rancher/shell 3.0.6 → 3.0.8-rc.1

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.
Files changed (146) hide show
  1. package/assets/images/pl/dark/rancher-logo.svg +131 -44
  2. package/assets/images/pl/rancher-logo.svg +120 -44
  3. package/assets/images/vendor/githubapp.svg +13 -0
  4. package/assets/styles/base/_basic.scss +2 -2
  5. package/assets/styles/base/_color-classic.scss +51 -0
  6. package/assets/styles/base/_color.scss +3 -3
  7. package/assets/styles/base/_mixins.scss +1 -1
  8. package/assets/styles/base/_typography.scss +1 -1
  9. package/assets/styles/base/_variables-classic.scss +47 -0
  10. package/assets/styles/global/_button.scss +49 -17
  11. package/assets/styles/global/_form.scss +1 -1
  12. package/assets/styles/themes/_dark.scss +4 -0
  13. package/assets/styles/themes/_light.scss +3 -69
  14. package/assets/styles/themes/_modern.scss +194 -50
  15. package/assets/styles/vendor/vue-select.scss +1 -2
  16. package/assets/translations/en-us.yaml +124 -32
  17. package/assets/translations/zh-hans.yaml +0 -4
  18. package/components/ClusterIconMenu.vue +1 -1
  19. package/components/ClusterProviderIcon.vue +1 -1
  20. package/components/CodeMirror.vue +1 -1
  21. package/components/IconOrSvg.vue +40 -29
  22. package/components/Inactivity.vue +222 -106
  23. package/components/InstallHelmCharts.vue +2 -2
  24. package/components/ResourceDetail/index.vue +2 -1
  25. package/components/SortableTable/index.vue +17 -2
  26. package/components/SortableTable/sorting.js +3 -1
  27. package/components/Tabbed/index.vue +5 -5
  28. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  29. package/components/fleet/FleetSecretSelector.vue +127 -0
  30. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  31. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  32. package/components/form/FileImageSelector.vue +13 -4
  33. package/components/form/FileSelector.vue +11 -2
  34. package/components/form/ResourceLabeledSelect.vue +1 -0
  35. package/components/form/ResourceTabs/index.vue +37 -18
  36. package/components/form/SecretSelector.vue +6 -2
  37. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  38. package/components/nav/Group.vue +29 -9
  39. package/components/nav/Header.vue +7 -8
  40. package/components/nav/NamespaceFilter.vue +1 -1
  41. package/components/nav/TopLevelMenu.helper.ts +47 -20
  42. package/components/nav/TopLevelMenu.vue +44 -14
  43. package/components/nav/Type.vue +0 -5
  44. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  45. package/config/pagination-table-headers.js +10 -2
  46. package/config/product/auth.js +1 -0
  47. package/config/product/explorer.js +4 -3
  48. package/config/query-params.js +1 -0
  49. package/config/settings.ts +8 -1
  50. package/config/table-headers.js +9 -0
  51. package/config/types.js +2 -0
  52. package/core/plugin.ts +18 -6
  53. package/core/types.ts +8 -0
  54. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  55. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  56. package/dialog/InstallExtensionDialog.vue +71 -45
  57. package/dialog/UninstallExtensionDialog.vue +2 -1
  58. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  59. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  60. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  61. package/edit/auth/github-app-steps.vue +97 -0
  62. package/edit/auth/github-steps.vue +75 -0
  63. package/edit/auth/github.vue +94 -65
  64. package/edit/auth/oidc.vue +86 -16
  65. package/edit/fleet.cattle.io.helmop.vue +51 -2
  66. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  67. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  68. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  69. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  70. package/list/projectsecret.vue +1 -1
  71. package/machine-config/azure.vue +1 -1
  72. package/mixins/__tests__/chart.test.ts +1 -1
  73. package/mixins/chart.js +2 -2
  74. package/models/__tests__/chart.test.ts +17 -9
  75. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  76. package/models/catalog.cattle.io.app.js +1 -1
  77. package/models/chart.js +3 -1
  78. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  79. package/models/event.js +7 -0
  80. package/models/management.cattle.io.authconfig.js +1 -0
  81. package/models/provisioning.cattle.io.cluster.js +9 -0
  82. package/package.json +2 -2
  83. package/pages/auth/login.vue +5 -2
  84. package/pages/auth/verify.vue +1 -1
  85. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  86. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  87. package/pages/c/_cluster/explorer/EventsTable.vue +92 -9
  88. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  89. package/pages/c/_cluster/settings/performance.vue +13 -26
  90. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  91. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  92. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  93. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  94. package/pages/home.vue +313 -12
  95. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  96. package/plugins/axios.js +2 -1
  97. package/plugins/dashboard-store/actions.js +4 -1
  98. package/plugins/dashboard-store/getters.js +1 -1
  99. package/plugins/dashboard-store/resource-class.js +20 -5
  100. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  101. package/plugins/steve/index.js +18 -10
  102. package/plugins/steve/mutations.js +2 -2
  103. package/plugins/steve/resourceWatcher.js +2 -2
  104. package/plugins/steve/steve-pagination-utils.ts +12 -9
  105. package/plugins/steve/subscribe.js +113 -85
  106. package/plugins/subscribe-events.ts +211 -0
  107. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  108. package/rancher-components/Banner/Banner.vue +2 -1
  109. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  110. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  111. package/scripts/extension/publish +1 -1
  112. package/store/auth.js +8 -3
  113. package/store/aws.js +8 -6
  114. package/store/features.js +1 -0
  115. package/store/index.js +21 -25
  116. package/store/prefs.js +6 -0
  117. package/types/extension-manager.ts +8 -1
  118. package/types/kube/kube-api.ts +2 -1
  119. package/types/rancher/index.d.ts +1 -0
  120. package/types/resources/settings.d.ts +52 -23
  121. package/types/shell/index.d.ts +412 -336
  122. package/types/store/subscribe-events.types.ts +70 -0
  123. package/types/store/subscribe.types.ts +6 -22
  124. package/utils/__tests__/cluster.test.ts +379 -1
  125. package/utils/cluster.js +157 -3
  126. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  127. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  128. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  129. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  130. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  131. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  132. package/utils/dynamic-content/config.ts +55 -0
  133. package/utils/dynamic-content/index.ts +273 -0
  134. package/utils/dynamic-content/info.ts +219 -0
  135. package/utils/dynamic-content/new-release.ts +126 -0
  136. package/utils/dynamic-content/support-notice.ts +169 -0
  137. package/utils/dynamic-content/types.d.ts +101 -0
  138. package/utils/dynamic-content/util.ts +122 -0
  139. package/utils/inactivity.ts +104 -0
  140. package/utils/pagination-utils.ts +105 -31
  141. package/utils/pagination-wrapper.ts +6 -8
  142. package/utils/release-notes.ts +1 -1
  143. package/utils/sort.js +5 -0
  144. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  145. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  146. package/utils/validators/formRules/index.ts +2 -2
@@ -0,0 +1,127 @@
1
+ <script lang="ts" setup>
2
+ import { ref, computed, defineProps, defineEmits } from 'vue';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import { TYPES } from '@shell/models/secret';
5
+ import { SECRET } from '@shell/config/types';
6
+ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
7
+ import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
8
+
9
+ interface Secret {
10
+ id?: string;
11
+ name: string;
12
+ namespace: string;
13
+ _type: string;
14
+ }
15
+
16
+ const props = defineProps({
17
+ value: {
18
+ type: Object,
19
+ required: true,
20
+ },
21
+ namespace: {
22
+ type: String,
23
+ required: true,
24
+ },
25
+ inStore: {
26
+ type: String,
27
+ default: 'management',
28
+ },
29
+ mode: {
30
+ type: String,
31
+ default: _EDIT
32
+ },
33
+ label: {
34
+ type: String,
35
+ default: '',
36
+ },
37
+ });
38
+
39
+ const emit = defineEmits(['update:value']);
40
+
41
+ const types = computed<string[]>(() => Object.values(TYPES));
42
+
43
+ const secrets = ref<Secret[]>([]);
44
+
45
+ const allSecretsSettings = {
46
+ updateResources: (secretsList: Secret[]) => {
47
+ const allSecretsInNamespace = secretsList.filter((secret) => types.value.includes(secret._type) && secret.namespace === props.namespace);
48
+ const mappedSecrets = mapSecrets(allSecretsInNamespace.sort((a, b) => a.name.localeCompare(b.name)));
49
+
50
+ secrets.value = allSecretsInNamespace;
51
+
52
+ return mappedSecrets;
53
+ }
54
+ };
55
+
56
+ const paginateSecretsSetting = {
57
+ requestSettings: paginatePageOptions,
58
+ updateResources: (secretsList: Secret[]) => {
59
+ const mappedSecrets = mapSecrets(secretsList);
60
+
61
+ secrets.value = secretsList;
62
+
63
+ return mappedSecrets;
64
+ }
65
+ };
66
+
67
+ function mapSecrets(secretsList: Secret[]) {
68
+ return secretsList.reduce<{ label: string; value: string }[]>((res, s) => {
69
+ if (s.id) {
70
+ res.push({ label: s.name, value: s.name });
71
+ } else {
72
+ res.push(s as any);
73
+ }
74
+
75
+ return res;
76
+ }, []);
77
+ }
78
+
79
+ function update(value: any) {
80
+ emit('update:value', value);
81
+ }
82
+
83
+ function paginatePageOptions(opts: any) {
84
+ const { opts: { filter } } = opts;
85
+
86
+ const filters = !!filter ? [PaginationParamFilter.createSingleField({
87
+ field: 'metadata.name', value: filter, exact: false, equals: true
88
+ })] : [];
89
+
90
+ filters.push(
91
+ PaginationParamFilter.createSingleField({ field: 'metadata.namespace', value: props.namespace }),
92
+ PaginationParamFilter.createMultipleFields(types.value.map((t) => ({
93
+ field: 'metadata.fields.1',
94
+ equals: true,
95
+ exact: true,
96
+ value: t
97
+ })))
98
+ );
99
+
100
+ return {
101
+ ...opts,
102
+ filters,
103
+ groupByNamespace: false,
104
+ classify: true,
105
+ sort: [{ asc: true, field: 'metadata.name' }],
106
+ };
107
+ }
108
+ </script>
109
+
110
+ <template>
111
+ <ResourceLabeledSelect
112
+ :key="namespace"
113
+ :value="value"
114
+ :label="label || t('fleet.secrets.label')"
115
+ :mode="mode"
116
+ :resource-type="SECRET"
117
+ :loading="$fetchState.pending"
118
+ :in-store="inStore"
119
+ :paginated-resource-settings="paginateSecretsSetting"
120
+ :all-resources-settings="allSecretsSettings"
121
+ :multiple="true"
122
+ @update:value="update"
123
+ />
124
+ </template>
125
+
126
+ <style lang="scss" scoped>
127
+ </style>
@@ -0,0 +1,125 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { _EDIT } from '@shell/config/query-params';
3
+ import FleetConfigMapSelector from '@shell/components/fleet/FleetConfigMapSelector.vue';
4
+ import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
5
+
6
+ describe('fleetConfigMapSelector.vue', () => {
7
+ const defaultProps = {
8
+ value: {},
9
+ namespace: 'fleet-default',
10
+ inStore: 'management',
11
+ mode: _EDIT,
12
+ label: 'Config Map',
13
+ };
14
+
15
+ const global = {
16
+ stubs: { ResourceLabeledSelect },
17
+ mocks: {
18
+ $fetchState: { pending: false },
19
+ $store: { getters: { 'management/all': () => [] } },
20
+ },
21
+ };
22
+
23
+ it('should emit update:value when update is called', async() => {
24
+ const wrapper = shallowMount(FleetConfigMapSelector, {
25
+ props: defaultProps,
26
+ global,
27
+ });
28
+
29
+ const vm = wrapper.vm as any;
30
+
31
+ await vm.update('cm1');
32
+
33
+ expect(wrapper.emitted('update:value')).toBeTruthy();
34
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual(['cm1']);
35
+ });
36
+
37
+ it('should correctly map configMaps', () => {
38
+ const wrapper = shallowMount(FleetConfigMapSelector, {
39
+ props: defaultProps,
40
+ global
41
+ });
42
+
43
+ const configMapsList = [
44
+ {
45
+ id: '1', name: 'cm1', namespace: 'fleet-default'
46
+ },
47
+ { name: 'cm2', namespace: 'fleet-default' }
48
+ ];
49
+
50
+ const vm = wrapper.vm as any;
51
+ const result = vm.mapConfigMaps(configMapsList);
52
+
53
+ expect(result).toStrictEqual([
54
+ { label: 'cm1', value: 'cm1' },
55
+ { name: 'cm2', namespace: 'fleet-default' }
56
+ ]);
57
+ });
58
+
59
+ it('should return correct filter options from paginatePageOptions', () => {
60
+ const wrapper = shallowMount(FleetConfigMapSelector, {
61
+ props: defaultProps,
62
+ global
63
+ });
64
+
65
+ const opts = { opts: { filter: 'test' } };
66
+
67
+ const vm = wrapper.vm as any;
68
+ const result = vm.paginatePageOptions(opts);
69
+
70
+ expect(result.filters).toHaveLength(2);
71
+ expect(result.groupByNamespace).toStrictEqual(false);
72
+ expect(result.classify).toStrictEqual(true);
73
+ expect(result.sort).toStrictEqual([{ asc: true, field: 'metadata.name' }]);
74
+ });
75
+
76
+ it('should correctly filter and map configMaps in allConfigMapsSettings.updateResources', () => {
77
+ const wrapper = shallowMount(FleetConfigMapSelector, {
78
+ props: defaultProps,
79
+ global
80
+ });
81
+
82
+ const configMapsList = [
83
+ {
84
+ id: '1', name: 'cm1', namespace: 'fleet-default'
85
+ },
86
+ {
87
+ id: '2', name: 'cm2', namespace: 'other'
88
+ }
89
+ ];
90
+
91
+ const vm = wrapper.vm as any;
92
+
93
+ const result = vm.allConfigMapsSettings.updateResources(configMapsList);
94
+
95
+ expect(result).toStrictEqual([{ label: 'cm1', value: 'cm1' }]);
96
+ expect(vm.configMaps).toStrictEqual([{
97
+ id: '1', name: 'cm1', namespace: 'fleet-default'
98
+ }]);
99
+ });
100
+
101
+ it('should correctly map configMaps in paginateConfigMapsSetting.updateResources', () => {
102
+ const wrapper = shallowMount(FleetConfigMapSelector, {
103
+ props: defaultProps,
104
+ global
105
+ });
106
+
107
+ const configMapsList = [
108
+ {
109
+ id: '1', name: 'cm1', namespace: 'fleet-default'
110
+ },
111
+ {
112
+ id: '2', name: 'cm2', namespace: 'fleet-default'
113
+ }
114
+ ];
115
+
116
+ const vm = wrapper.vm as any;
117
+ const result = vm.paginateConfigMapsSetting.updateResources(configMapsList);
118
+
119
+ expect(result).toStrictEqual([
120
+ { label: 'cm1', value: 'cm1' },
121
+ { label: 'cm2', value: 'cm2' }
122
+ ]);
123
+ expect(vm.configMaps).toStrictEqual(configMapsList);
124
+ });
125
+ });
@@ -0,0 +1,82 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import FleetSecretSelector from '@shell/components/fleet/FleetSecretSelector.vue';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+
5
+ describe('component: FleetSecretSelector.vue', () => {
6
+ const secretsMock = [
7
+ {
8
+ id: '1', name: 'secret1', namespace: 'fleet-default', _type: 'Opaque'
9
+ },
10
+ {
11
+ id: '2', name: 'secret2', namespace: 'fleet-default', _type: 'Opaque'
12
+ },
13
+ {
14
+ id: '3', name: 'secret3', namespace: 'other', _type: 'Opaque'
15
+ },
16
+ ];
17
+
18
+ const props = {
19
+ value: {},
20
+ namespace: 'fleet-default',
21
+ inStore: 'management',
22
+ mode: _EDIT,
23
+ };
24
+
25
+ const global = { mocks: { $fetchState: { pending: false, error: false } } };
26
+
27
+ it('should emit update:value when update is called', async() => {
28
+ const wrapper = shallowMount(FleetSecretSelector, { props, global });
29
+
30
+ await (wrapper.vm as any).update('secret1');
31
+
32
+ expect(wrapper.emitted('update:value')).toBeTruthy();
33
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual(['secret1']);
34
+ });
35
+
36
+ it('should correctly map secrets', () => {
37
+ const wrapper = shallowMount(FleetSecretSelector, { props, global });
38
+ const mapped = (wrapper.vm as any).mapSecrets(secretsMock);
39
+
40
+ expect(mapped).toStrictEqual([
41
+ { label: 'secret1', value: 'secret1' },
42
+ { label: 'secret2', value: 'secret2' },
43
+ { label: 'secret3', value: 'secret3' }
44
+ ]);
45
+ });
46
+
47
+ it('should filter and sort secrets by namespace and type', () => {
48
+ const wrapper = shallowMount(FleetSecretSelector, { props, global });
49
+
50
+ const result = (wrapper.vm as any).allSecretsSettings.updateResources(secretsMock);
51
+
52
+ expect(result).toStrictEqual([
53
+ { label: 'secret1', value: 'secret1' },
54
+ { label: 'secret2', value: 'secret2' }
55
+ ]);
56
+ expect((wrapper.vm as any).secrets).toStrictEqual([
57
+ {
58
+ id: '1', name: 'secret1', namespace: 'fleet-default', _type: 'Opaque'
59
+ },
60
+ {
61
+ id: '2', name: 'secret2', namespace: 'fleet-default', _type: 'Opaque'
62
+ }
63
+ ]);
64
+ });
65
+
66
+ it('should return correct filter structure from paginatePageOptions', () => {
67
+ const wrapper = shallowMount(FleetSecretSelector, { props, global });
68
+
69
+ const opts = { opts: { filter: 'secret' } };
70
+ const result = (wrapper.vm as any).paginatePageOptions(opts);
71
+
72
+ expect(result.filters).toStrictEqual(
73
+ expect.arrayContaining([
74
+ expect.objectContaining({ fields: expect.arrayContaining([expect.objectContaining({ field: 'metadata.name' })]) }),
75
+ expect.objectContaining({ fields: expect.arrayContaining([expect.objectContaining({ field: 'metadata.namespace' })]) }),
76
+ expect.objectContaining({ fields: expect.arrayContaining([expect.objectContaining({ field: 'metadata.fields.1' })]) }),
77
+ ])
78
+ );
79
+
80
+ expect(result.sort).toStrictEqual([{ asc: true, field: 'metadata.name' }]);
81
+ });
82
+ });
@@ -7,7 +7,8 @@ export default {
7
7
  emits: ['update:value', 'error'],
8
8
 
9
9
  components: { FileSelector, LazyImage },
10
- props: {
10
+
11
+ props: {
11
12
  value: {
12
13
  type: String,
13
14
  default: null,
@@ -36,12 +37,20 @@ export default {
36
37
  accept: {
37
38
  type: String,
38
39
  default: 'image/*'
39
- }
40
+ },
41
+
42
+ class: {
43
+ type: [String, Array],
44
+ default: 'role-primary',
45
+ },
40
46
  },
41
47
  computed: {
42
48
  isView() {
43
49
  return this.mode === _VIEW;
44
- }
50
+ },
51
+ customClass() {
52
+ return [...(Array.isArray(this.class) ? this.class : [this.class])];
53
+ },
45
54
  },
46
55
  methods: {
47
56
  /**
@@ -62,7 +71,7 @@ export default {
62
71
  <FileSelector
63
72
  v-if="!value && !isView"
64
73
  :value="value"
65
- class="btn role-primary"
74
+ :class="customClass"
66
75
  :mode="mode"
67
76
  :read-as-data-url="true"
68
77
  :byte-limit="byteLimit"
@@ -67,12 +67,21 @@ export default {
67
67
  default: '*'
68
68
  },
69
69
 
70
+ class: {
71
+ type: [String, Array],
72
+ default: () => [],
73
+ }
74
+
70
75
  },
71
76
 
72
77
  computed: {
73
78
  isView() {
74
79
  return this.mode === _VIEW;
75
- }
80
+ },
81
+
82
+ customClass() {
83
+ return ['file-selector', 'btn', ...(Array.isArray(this.class) ? this.class : [this.class])];
84
+ },
76
85
  },
77
86
 
78
87
  methods: {
@@ -151,7 +160,7 @@ export default {
151
160
  :aria-label="label"
152
161
  type="button"
153
162
  role="button"
154
- class="file-selector btn"
163
+ :class="customClass"
155
164
  data-testid="file-selector__uploader-button"
156
165
  @click="selectFile"
157
166
  >
@@ -163,6 +163,7 @@ export default defineComponent({
163
163
  :loading="$fetchState.pending"
164
164
  :options="allOfType"
165
165
  :paginate="paginateType"
166
+ :multiple="$attrs.multiple || false"
166
167
  @update:value="$emit('update:value', $event)"
167
168
  />
168
169
  </template>
@@ -7,14 +7,14 @@ import Tabbed from '@shell/components/Tabbed';
7
7
  import Tab from '@shell/components/Tabbed/Tab';
8
8
  import CreateEditView from '@shell/mixins/create-edit-view';
9
9
  import Conditions from '@shell/components/form/Conditions';
10
- import { EVENT } from '@shell/config/types';
10
+ import { EVENT, NAMESPACE } from '@shell/config/types';
11
11
  import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
12
12
  import { _VIEW } from '@shell/config/query-params';
13
13
  import RelatedResources from '@shell/components/RelatedResources';
14
14
  import { isConditionReadyAndWaiting } from '@shell/plugins/dashboard-store/resource-class';
15
15
  import { PaginationParamFilter } from '@shell/types/store/pagination.types';
16
16
  import { MESSAGE, REASON } from '@shell/config/table-headers';
17
- import { STEVE_EVENT_LAST_SEEN, STEVE_EVENT_TYPE, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
17
+ import { STEVE_EVENT_FIRST_SEEN, 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
19
  import { useIndicateUseCounts } from '@shell/components/form/ResourceTabs/composable';
20
20
 
@@ -93,7 +93,7 @@ export default {
93
93
  headerFromSchemaColString('Subobject', eventSchema, this.$store.getters, true),
94
94
  headerFromSchemaColString('Source', eventSchema, this.$store.getters, true),
95
95
  MESSAGE,
96
- headerFromSchemaColString('First Seen', eventSchema, this.$store.getters, true),
96
+ STEVE_EVENT_FIRST_SEEN,
97
97
  headerFromSchemaColString('Count', eventSchema, this.$store.getters, true),
98
98
  STEVE_NAME_COL,
99
99
  ] : [];
@@ -119,6 +119,9 @@ export default {
119
119
  },
120
120
 
121
121
  computed: {
122
+ isNamespace() {
123
+ return this.value?.type === NAMESPACE;
124
+ },
122
125
  showEvents() {
123
126
  return this.isView && this.needEvents && this.eventSchema;
124
127
  },
@@ -190,7 +193,9 @@ export default {
190
193
  * Filter out hidden repos from list of all repos
191
194
  */
192
195
  filterEventsLocal(rows) {
193
- return rows.filter((event) => event.involvedObject?.uid === this.value?.metadata?.uid);
196
+ return rows.filter((event) => {
197
+ return this.isNamespace ? event.metadata?.namespace === this.value?.metadata?.name : event.involvedObject?.uid === this.value?.metadata?.uid;
198
+ });
194
199
  },
195
200
 
196
201
  /**
@@ -204,27 +209,22 @@ export default {
204
209
  pagination.filters = [];
205
210
  }
206
211
 
207
- const field = `involvedObject.uid`;
208
-
209
- // of type PaginationParamFilter
210
- let existing = null;
211
-
212
- for (let i = 0; i < pagination.filters.length; i++) {
213
- const filter = pagination.filters[i];
212
+ // Determine the field and value based on type
213
+ const field = this.isNamespace ? 'metadata.namespace' : 'involvedObject.uid';
214
+ const value = this.isNamespace ? this.value.metadata.name : this.value.metadata.uid;
214
215
 
215
- if (!!filter.fields.find((f) => f.field === field)) {
216
- existing = filter;
217
- break;
218
- }
219
- }
216
+ // Check if a filter for this field already exists
217
+ const existing = pagination.filters.find((f) => f.fields.some((ff) => ff.field === field));
220
218
 
219
+ // Create the required filter
221
220
  const required = PaginationParamFilter.createSingleField({
222
221
  field,
223
222
  exact: true,
224
- value: this.value.metadata.uid,
223
+ value,
225
224
  equals: true
226
225
  });
227
226
 
227
+ // Merge or add the filter
228
228
  if (!!existing) {
229
229
  Object.assign(existing, required);
230
230
  } else {
@@ -260,10 +260,16 @@ export default {
260
260
 
261
261
  <Tab
262
262
  v-if="showEvents"
263
- label-key="resourceTabs.events.tab"
263
+ :label="isNamespace ? t('resourceTabs.events.namespaceTab') : t('resourceTabs.events.tab')"
264
264
  name="events"
265
265
  :weight="-2"
266
266
  >
267
+ <!-- Caption for namespace pages -->
268
+ <div
269
+ v-if="isNamespace"
270
+ v-clean-html="t('resourceTabs.events.namespaceCaption', { namespace: value.metadata.name }, true)"
271
+ class="tab-caption"
272
+ />
267
273
  <!-- namespaced: false given we don't want the default handling of namespaced resource (apply header filter) -->
268
274
  <PaginatedResourceTable
269
275
  :schema="eventSchema"
@@ -318,4 +324,17 @@ export default {
318
324
  }
319
325
  }
320
326
  }
327
+ /* Caption for namespace events tab */
328
+ .tab-caption {
329
+ align-items: center;
330
+ font-size: 16px;
331
+ margin-bottom: 24px;
332
+
333
+ .namespace-name {
334
+ display: inline;
335
+ font-weight: bold;
336
+ margin-right: 0 4px;
337
+ white-space: nowrap;
338
+ }
339
+ }
321
340
  </style>
@@ -61,7 +61,7 @@ export default {
61
61
  },
62
62
  inStore: {
63
63
  type: String,
64
- default: 'cluster',
64
+ default: undefined,
65
65
  }
66
66
  },
67
67
 
@@ -129,6 +129,10 @@ export default {
129
129
  }));
130
130
  },
131
131
 
132
+ validInStore() {
133
+ return this.inStore || this.$store.getters['currentStore']() || 'cluster';
134
+ },
135
+
132
136
  isView() {
133
137
  return this.mode === _VIEW;
134
138
  },
@@ -221,7 +225,7 @@ export default {
221
225
  :label="secretNameLabel"
222
226
  :mode="mode"
223
227
  :resource-type="SECRET"
224
- :in-store="inStore"
228
+ :in-store="validInStore"
225
229
  :paginated-resource-settings="paginateSecretsSetting"
226
230
  :all-resources-settings="allSecretsSettings"
227
231
  />
@@ -0,0 +1,90 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
3
+ import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
4
+ import { RESOURCE_LABEL_SELECT_MODE } from '@shell/types/components/resourceLabeledSelect';
5
+
6
+ const mockStore = {
7
+ getters: {
8
+ currentStore: jest.fn().mockReturnValue('cluster'),
9
+ 'cluster/paginationEnabled': jest.fn().mockReturnValue(true),
10
+ 'cluster/all': jest.fn().mockReturnValue([{ id: 'foo', name: 'Foo' }]),
11
+ },
12
+ dispatch: jest.fn().mockResolvedValue(undefined),
13
+ };
14
+
15
+ const requiredSetup = () => {
16
+ return {
17
+ global: {
18
+ components: { LabeledSelect },
19
+ mocks: { $store: mockStore, $fetchState: {} }
20
+ }
21
+ };
22
+ };
23
+
24
+ describe('component: ResourceLabeledSelect.vue', () => {
25
+ it('should render LabeledSelect', async() => {
26
+ const wrapper = shallowMount(ResourceLabeledSelect, {
27
+ ...requiredSetup(),
28
+ props: { resourceType: 'testResource' }
29
+ });
30
+
31
+ expect(wrapper.findComponent(LabeledSelect).exists()).toBe(true);
32
+ });
33
+
34
+ it('should call paginateType with overrideRequest if provided', async() => {
35
+ const overrideRequest = jest.fn().mockResolvedValue({ page: [{ id: 'bar', name: 'Bar' }], total: 1 });
36
+ const wrapper = shallowMount(ResourceLabeledSelect, {
37
+ ...requiredSetup(),
38
+ props: {
39
+ resourceType: 'testResource',
40
+ paginateMode: RESOURCE_LABEL_SELECT_MODE.DYNAMIC,
41
+ paginatedResourceSettings: { overrideRequest }
42
+ }
43
+ });
44
+
45
+ const result = await wrapper.vm.paginateType({
46
+ filter: 'bar',
47
+ page: 1,
48
+ pageSize: 10,
49
+ pageContent: [],
50
+ resetPage: false
51
+ });
52
+
53
+ expect(overrideRequest).toHaveBeenCalledWith({
54
+ filter: 'bar',
55
+ page: 1,
56
+ pageSize: 10,
57
+ pageContent: [],
58
+ resetPage: false
59
+ });
60
+ expect(result.page[0].name).toBe('Bar');
61
+ });
62
+
63
+ it('should emit update:value when LabeledSelect emits update:value', async() => {
64
+ const wrapper = shallowMount(ResourceLabeledSelect, {
65
+ ...requiredSetup(),
66
+ props: { resourceType: 'testResource' }
67
+ });
68
+
69
+ wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'baz');
70
+
71
+ expect(wrapper.emitted('update:value')).toBeTruthy();
72
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual(['baz']);
73
+ });
74
+
75
+ it('should pass correct props and attrs to LabeledSelect', async() => {
76
+ const wrapper = shallowMount(ResourceLabeledSelect, {
77
+ ...requiredSetup(),
78
+ props: {
79
+ resourceType: 'testResource',
80
+ allResourcesSettings: { labelSelectOptions: { placeholder: 'Select a resource' } }
81
+ },
82
+ attrs: { multiple: true }
83
+ });
84
+
85
+ const labeledSelect = wrapper.findComponent(LabeledSelect);
86
+
87
+ expect(labeledSelect.attributes('placeholder')).toBe('Select a resource');
88
+ expect(labeledSelect.attributes('multiple')).toBe('true');
89
+ });
90
+ });