@rancher/shell 3.0.6 → 3.0.7

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 (78) 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/styles/base/_basic.scss +2 -2
  4. package/assets/styles/base/_color-classic.scss +51 -0
  5. package/assets/styles/base/_color.scss +3 -3
  6. package/assets/styles/base/_mixins.scss +1 -1
  7. package/assets/styles/base/_variables-classic.scss +47 -0
  8. package/assets/styles/global/_button.scss +49 -17
  9. package/assets/styles/global/_form.scss +1 -1
  10. package/assets/styles/themes/_dark.scss +4 -0
  11. package/assets/styles/themes/_light.scss +3 -69
  12. package/assets/styles/themes/_modern.scss +194 -50
  13. package/assets/styles/vendor/vue-select.scss +1 -2
  14. package/assets/translations/en-us.yaml +33 -21
  15. package/components/ClusterIconMenu.vue +1 -1
  16. package/components/ClusterProviderIcon.vue +1 -1
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/IconOrSvg.vue +40 -29
  19. package/components/ResourceDetail/index.vue +1 -0
  20. package/components/SortableTable/sorting.js +3 -1
  21. package/components/Tabbed/index.vue +5 -5
  22. package/components/form/ResourceTabs/index.vue +37 -18
  23. package/components/form/SecretSelector.vue +6 -2
  24. package/components/nav/Group.vue +29 -9
  25. package/components/nav/Header.vue +6 -8
  26. package/components/nav/NamespaceFilter.vue +1 -1
  27. package/components/nav/TopLevelMenu.helper.ts +47 -20
  28. package/components/nav/TopLevelMenu.vue +44 -14
  29. package/components/nav/Type.vue +0 -5
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  31. package/config/pagination-table-headers.js +10 -2
  32. package/config/product/explorer.js +4 -3
  33. package/config/table-headers.js +9 -0
  34. package/core/plugin.ts +18 -6
  35. package/core/types.ts +8 -0
  36. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  37. package/dialog/InstallExtensionDialog.vue +71 -45
  38. package/dialog/UninstallExtensionDialog.vue +2 -1
  39. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  40. package/edit/auth/oidc.vue +86 -16
  41. package/mixins/__tests__/chart.test.ts +1 -1
  42. package/mixins/chart.js +1 -1
  43. package/models/event.js +7 -0
  44. package/models/provisioning.cattle.io.cluster.js +9 -0
  45. package/package.json +1 -1
  46. package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
  47. package/pages/c/_cluster/settings/performance.vue +1 -1
  48. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  49. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  50. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  51. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  52. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  53. package/plugins/dashboard-store/actions.js +3 -0
  54. package/plugins/dashboard-store/getters.js +1 -1
  55. package/plugins/dashboard-store/resource-class.js +3 -3
  56. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  57. package/plugins/steve/index.js +18 -10
  58. package/plugins/steve/mutations.js +2 -2
  59. package/plugins/steve/resourceWatcher.js +2 -2
  60. package/plugins/steve/steve-pagination-utils.ts +12 -9
  61. package/plugins/steve/subscribe.js +113 -85
  62. package/plugins/subscribe-events.ts +211 -0
  63. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  64. package/rancher-components/Banner/Banner.vue +2 -1
  65. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  66. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  67. package/store/index.js +12 -22
  68. package/types/extension-manager.ts +8 -1
  69. package/types/resources/settings.d.ts +24 -17
  70. package/types/shell/index.d.ts +352 -335
  71. package/types/store/subscribe-events.types.ts +70 -0
  72. package/types/store/subscribe.types.ts +6 -22
  73. package/utils/pagination-utils.ts +87 -28
  74. package/utils/pagination-wrapper.ts +6 -8
  75. package/utils/sort.js +5 -0
  76. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  77. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  78. package/utils/validators/formRules/index.ts +2 -2
@@ -0,0 +1,70 @@
1
+ import { STEVE_WATCH_EVENT_TYPES, STEVE_WATCH_EVENT_TYPES_NAMES, STEVE_WATCH_PARAMS } from '@shell/types/store/subscribe.types';
2
+
3
+ /**
4
+ * Common params used when a watcher adds or removes a listener to a watch
5
+ */
6
+ export interface STEVE_WATCH_EVENT_PARAMS_COMMON {
7
+ event: STEVE_WATCH_EVENT_TYPES,
8
+ id: string,
9
+ /**
10
+ * of type @STEVE_WATCH_PARAMS
11
+ */
12
+ params: STEVE_WATCH_PARAMS,
13
+ }
14
+
15
+ /**
16
+ * Executes when a watch event has a listener and it's triggered
17
+ */
18
+ export type STEVE_WATCH_EVENT_LISTENER_CALLBACK = () => void
19
+
20
+ /**
21
+ * Common params used when a watcher adds a listener to a watch
22
+ */
23
+ export interface STEVE_WATCH_EVENT_PARAMS extends STEVE_WATCH_EVENT_PARAMS_COMMON {
24
+ callback: STEVE_WATCH_EVENT_LISTENER_CALLBACK,
25
+ }
26
+
27
+ /**
28
+ * Common params used when a watcher removes a listener from a watch
29
+ */
30
+ export type STEVE_UNWATCH_EVENT_PARAMS = STEVE_WATCH_EVENT_PARAMS_COMMON
31
+
32
+ /**
33
+ * Common properties for identifying a subscribe watcher
34
+ */
35
+ export interface SubscribeEventWatchArgs {
36
+ params: STEVE_WATCH_PARAMS,
37
+ }
38
+
39
+ /**
40
+ * Common properties for identifying a subscribe watcher's listeners
41
+ */
42
+ export interface SubscribeEventListenerArgs extends SubscribeEventWatchArgs {
43
+ event: STEVE_WATCH_EVENT_TYPES_NAMES,
44
+ }
45
+
46
+ /**
47
+ * Common properties for identifying a subscribe watcher's listener
48
+ */
49
+ export interface SubscribeEventCallbackArgs extends SubscribeEventListenerArgs {
50
+ id: string,
51
+ }
52
+
53
+ /**
54
+ * Represents a subscribe watcher listener
55
+ */
56
+ export interface SubscribeEventListener {
57
+ event: STEVE_WATCH_EVENT_TYPES_NAMES,
58
+ callbacks: { [id: string]: STEVE_WATCH_EVENT_LISTENER_CALLBACK},
59
+ }
60
+
61
+ /**
62
+ * Represents a subscribe watcher
63
+ */
64
+ export type SubscribeEventWatch = {
65
+ /**
66
+ * is there a standard non-listener watch for this type (i.e. vanilla watch)
67
+ */
68
+ hasStandardWatch: boolean,
69
+ listeners: SubscribeEventListener[],
70
+ }
@@ -6,7 +6,7 @@ export enum STEVE_WATCH_MODE {
6
6
  /* eslint-enable no-unused-vars */
7
7
 
8
8
  /* eslint-disable no-unused-vars */
9
- export enum STEVE_WATCH_EVENT {
9
+ export enum STEVE_WATCH_EVENT_TYPES {
10
10
  START = 'resource.start',
11
11
  CREATE = 'resource.create',
12
12
  CHANGE = 'resource.change',
@@ -17,6 +17,11 @@ export enum STEVE_WATCH_EVENT {
17
17
  }
18
18
  /* eslint-enable no-unused-vars */
19
19
 
20
+ export type STEVE_WATCH_EVENT_TYPES_NAMES = `${ STEVE_WATCH_EVENT_TYPES }`;
21
+
22
+ /**
23
+ * The content of the web socket messages sent (and partially received back from) steve
24
+ */
20
25
  export interface STEVE_WATCH_PARAMS {
21
26
  type: string,
22
27
  selector?: string,
@@ -27,24 +32,3 @@ export interface STEVE_WATCH_PARAMS {
27
32
  force?: boolean,
28
33
  mode?: STEVE_WATCH_MODE
29
34
  }
30
-
31
- export type STEVE_WATCH_EVENT_LISTENER_CALLBACK = () => void
32
- export interface STEVE_WATCH_EVENT_LISTENER {
33
- params: STEVE_WATCH_PARAMS,
34
- callbacks: { [id: string]: STEVE_WATCH_EVENT_LISTENER_CALLBACK},
35
- }
36
-
37
- export interface STEVE_WATCH_EVENT_PARAMS_COMMON {
38
- event: STEVE_WATCH_EVENT,
39
- id: string,
40
- /**
41
- * of type @STEVE_WATCH_PARAMS
42
- */
43
- params: STEVE_WATCH_PARAMS,
44
- }
45
-
46
- export interface STEVE_WATCH_EVENT_PARAMS extends STEVE_WATCH_EVENT_PARAMS_COMMON {
47
- callback: STEVE_WATCH_EVENT_LISTENER_CALLBACK,
48
- }
49
-
50
- export type STEVE_UNWATCH_EVENT_PARAMS = STEVE_WATCH_EVENT_PARAMS_COMMON
@@ -1,4 +1,4 @@
1
- import { PaginationFeature, PaginationSettings, PaginationSettingsStore } from '@shell/types/resources/settings';
1
+ import { PaginationFeature, PaginationSettings, PaginationSettingsStore, PaginationSettingsStores } from '@shell/types/resources/settings';
2
2
  import {
3
3
  NAMESPACE_FILTER_ALL_USER as ALL_USER,
4
4
  NAMESPACE_FILTER_ALL as ALL,
@@ -16,6 +16,10 @@ import { STEVE_CACHE } from '@shell/store/features';
16
16
  import { getPerformanceSetting } from '@shell/utils/settings';
17
17
  import { PAGINATION_SETTINGS_STORE_DEFAULTS } from '@shell/plugins/steve/steve-pagination-utils';
18
18
  import { MANAGEMENT } from '@shell/config/types';
19
+ import { VuexStore } from '@shell/types/store/vuex';
20
+ import { ServerSidePaginationExtensionConfig } from '@shell/core/types';
21
+ import { EXT_IDS } from '@shell/core/plugin';
22
+ import { ExtensionManager } from '@shell/types/extension-manager';
19
23
  import { DEFAULT_PERF_SETTING } from '@shell/config/settings';
20
24
 
21
25
  /**
@@ -39,9 +43,9 @@ class PaginationUtils {
39
43
  return perf.serverPagination;
40
44
  }
41
45
 
42
- public getStoreSettings(ctx: any): PaginationSettingsStore
43
- public getStoreSettings(serverPagination: PaginationSettings): PaginationSettingsStore
44
- public getStoreSettings(arg: any | PaginationSettings): PaginationSettingsStore {
46
+ public getStoreSettings(ctx: any): PaginationSettingsStores
47
+ public getStoreSettings(serverPagination: PaginationSettings): PaginationSettingsStores
48
+ public getStoreSettings(arg: any | PaginationSettings): PaginationSettingsStores {
45
49
  const serverPagination: PaginationSettings = arg?.rootGetters !== undefined ? this.getSettings(arg) : arg;
46
50
 
47
51
  // Ensure we use the current default store settings if
@@ -55,7 +59,7 @@ class PaginationUtils {
55
59
  return serverPagination?.stores || this.getStoreDefault();
56
60
  }
57
61
 
58
- public getStoreDefault(): PaginationSettingsStore {
62
+ public getStoreDefault(): PaginationSettingsStores {
59
63
  return PAGINATION_SETTINGS_STORE_DEFAULTS;
60
64
  }
61
65
 
@@ -82,28 +86,17 @@ class PaginationUtils {
82
86
  }
83
87
 
84
88
  /**
85
- * Is pagination enabled at a global level or for a specific resource
89
+ * Helper - check if a specific resource in a specific store is enabled given the provided settings
86
90
  */
87
- isEnabled({ rootGetters }: any, enabledFor: PaginationResourceContext) {
88
- // Cache must be enabled to support pagination api
89
- if (!this.isSteveCacheEnabled({ rootGetters })) {
90
- return false;
91
- }
92
-
93
- const settings = this.getSettings({ rootGetters });
94
-
95
- // No setting, not enabled
96
- if (!settings) {
97
- return false;
98
- }
99
-
100
- // Missing required params, not enabled
101
- if (!enabledFor) {
102
- return false;
103
- }
104
-
105
- const storeSettings = this.getStoreSettings(settings)?.[enabledFor.store];
106
-
91
+ private isEnabledInStore({
92
+ ctx: { rootGetters },
93
+ storeSettings,
94
+ enabledFor
95
+ }: {
96
+ ctx: Partial<VuexStore>,
97
+ storeSettings: PaginationSettingsStore,
98
+ enabledFor: PaginationResourceContext
99
+ }): boolean {
107
100
  // No pagination setting for target store, not enabled
108
101
  if (!storeSettings) {
109
102
  return false;
@@ -130,16 +123,19 @@ class PaginationUtils {
130
123
  !rootGetters['type-map/configuredPaginationHeaders'](enabledFor.resource.id) &&
131
124
  !rootGetters['type-map/hasCustomList'](enabledFor.resource.id);
132
125
 
133
- if (storeSettings.resources.enableSome.generic && isGeneric) {
126
+ // Store says generic resource with no custom pagination settings are supported
127
+ if (storeSettings.resources.enableSome?.generic && isGeneric) {
134
128
  return true;
135
129
  }
136
130
 
137
- if (storeSettings.resources.enableSome.enabled.find((setting) => {
131
+ // Store says some specific resources are enabled
132
+ if (storeSettings.resources.enableSome?.enabled?.find((setting) => {
138
133
  if (typeof setting === 'string') {
139
134
  return setting === enabledFor.resource?.id;
140
135
  }
141
136
 
142
137
  if (setting.resource === enabledFor.resource?.id) {
138
+ // Store says only specific usages of this resource are enabled
143
139
  if (!!setting.context) {
144
140
  return enabledFor.resource?.context ? setting.context.includes(enabledFor.resource.context) : false;
145
141
  }
@@ -155,6 +151,69 @@ class PaginationUtils {
155
151
  return false;
156
152
  }
157
153
 
154
+ /**
155
+ * Is pagination enabled at a global level or for a specific resource
156
+ */
157
+ isEnabled({ rootGetters, $plugin }: any, enabledFor: PaginationResourceContext) {
158
+ // Cache must be enabled to support pagination api
159
+ if (!this.isSteveCacheEnabled({ rootGetters })) {
160
+ return false;
161
+ }
162
+
163
+ const settings = this.getSettings({ rootGetters });
164
+
165
+ // No setting, not enabled
166
+ if (!settings) {
167
+ return false;
168
+ }
169
+
170
+ // Missing required params, not enabled
171
+ if (!enabledFor) {
172
+ return false;
173
+ }
174
+
175
+ // Does an extension say this type is enabled?
176
+ const plugin = $plugin as ExtensionManager;
177
+ const paginationExtensionPoints = plugin.getAll()[EXT_IDS.SERVER_SIDE_PAGINATION_RESOURCES];
178
+
179
+ if (paginationExtensionPoints) {
180
+ const allowed = Object.entries(paginationExtensionPoints).find(([_, settingsFn]) => {
181
+ if (!settingsFn) {
182
+ return false;
183
+ }
184
+
185
+ const settings: ServerSidePaginationExtensionConfig = settingsFn();
186
+ const allowed = Object.entries(settings).find(([store, settings]) => {
187
+ if (store !== enabledFor.store) {
188
+ return false;
189
+ }
190
+
191
+ return this.isEnabledInStore({
192
+ ctx: { rootGetters },
193
+ storeSettings: settings,
194
+ enabledFor
195
+ });
196
+ });
197
+
198
+ if (allowed) {
199
+ return true;
200
+ }
201
+ });
202
+
203
+ if (allowed) {
204
+ return true;
205
+ }
206
+ }
207
+
208
+ const storeSettings = this.getStoreSettings(settings)?.[enabledFor.store];
209
+
210
+ return this.isEnabledInStore({
211
+ ctx: { rootGetters },
212
+ storeSettings,
213
+ enabledFor
214
+ });
215
+ }
216
+
158
217
  listAutoRefreshToggleEnabled({ rootGetters }: any): boolean {
159
218
  return this.isFeatureEnabled({ rootGetters }, 'listAutoRefreshToggle');
160
219
  }
@@ -2,10 +2,9 @@ import paginationUtils from '@shell/utils/pagination-utils';
2
2
  import { PaginationArgs, PaginationResourceContext } from '@shell/types/store/pagination.types';
3
3
  import { VuexStore } from '@shell/types/store/vuex';
4
4
  import { ActionFindPageArgs, ActionFindPageTransientResult } from '@shell/types/store/dashboard-store.types';
5
- import {
6
- STEVE_WATCH_EVENT_LISTENER_CALLBACK, STEVE_UNWATCH_EVENT_PARAMS, STEVE_WATCH_EVENT, STEVE_WATCH_EVENT_PARAMS, STEVE_WATCH_EVENT_PARAMS_COMMON, STEVE_WATCH_MODE
7
- } from '@shell/types/store/subscribe.types';
5
+ import { STEVE_WATCH_EVENT_TYPES, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
8
6
  import { Reactive, reactive } from 'vue';
7
+ import { STEVE_UNWATCH_EVENT_PARAMS, STEVE_WATCH_EVENT_LISTENER_CALLBACK, STEVE_WATCH_EVENT_PARAMS, STEVE_WATCH_EVENT_PARAMS_COMMON } from '@shell/types/store/subscribe-events.types';
9
8
 
10
9
  interface Args {
11
10
  $store: VuexStore,
@@ -70,7 +69,7 @@ class PaginationWrapper<T extends object> {
70
69
  this.classify = formatResponse?.classify || false;
71
70
  this.reactive = formatResponse?.reactive || false;
72
71
 
73
- this.isEnabled = paginationUtils.isEnabled({ rootGetters: $store.getters }, enabledFor);
72
+ this.isEnabled = paginationUtils.isEnabled({ rootGetters: $store.getters, $plugin: this.$store.$plugin }, enabledFor);
74
73
  }
75
74
 
76
75
  async request(args: {
@@ -92,7 +91,7 @@ class PaginationWrapper<T extends object> {
92
91
  // Watch
93
92
  if (this.onChange && !this.steveWatchParams) {
94
93
  this.steveWatchParams = {
95
- event: STEVE_WATCH_EVENT.CHANGES,
94
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
96
95
  id: this.id,
97
96
  params: {
98
97
  type: this.enabledFor.resource?.id as string,
@@ -126,7 +125,7 @@ class PaginationWrapper<T extends object> {
126
125
  }
127
126
  const watchParams: STEVE_WATCH_EVENT_PARAMS = {
128
127
  ...this.steveWatchParams,
129
- callback: this.onChange as STEVE_WATCH_EVENT_LISTENER_CALLBACK, // we must have it by now
128
+ callback: this.onChange as STEVE_WATCH_EVENT_LISTENER_CALLBACK, // we must have onChange by now
130
129
  };
131
130
 
132
131
  await this.$store.dispatch(`${ this.enabledFor.store }/watchEvent`, watchParams);
@@ -134,8 +133,7 @@ class PaginationWrapper<T extends object> {
134
133
 
135
134
  private async unWatch() {
136
135
  if (!this.steveWatchParams) {
137
- console.error('Calling unWatch but no watch params created'); // eslint-disable-line no-console
138
-
136
+ // We're unwatching before we've made the initial request
139
137
  return;
140
138
  }
141
139
 
package/utils/sort.js CHANGED
@@ -169,6 +169,11 @@ export function compare(a, b) {
169
169
  return 0;
170
170
  }
171
171
 
172
+ /**
173
+ * Should the logic of this sort field be flipped?
174
+ *
175
+ * For instance show descending but sort by ascending
176
+ */
172
177
  export function parseField(str) {
173
178
  const parts = str.split(/:/);
174
179
 
@@ -0,0 +1,283 @@
1
+ import { STEVE_CACHE } from '@shell/store/features';
2
+ import { SETTING } from '@shell/config/settings';
3
+ import { EXT_IDS } from '@shell/core/plugin';
4
+ import { PaginationResourceContext } from '@shell/types/store/pagination.types';
5
+ import { PaginationSettings, PaginationSettingsStore } from '@shell/types/resources/settings';
6
+ import paginationUtils from '@shell/utils/pagination-utils';
7
+
8
+ describe('pagination-utils', () => {
9
+ describe('isEnabledInStore', () => {
10
+ const mockRootGetters = {
11
+ 'type-map/configuredHeaders': jest.fn(),
12
+ 'type-map/configuredPaginationHeaders': jest.fn(),
13
+ 'type-map/hasCustomList': jest.fn(),
14
+ };
15
+
16
+ beforeEach(() => {
17
+ jest.resetAllMocks();
18
+ });
19
+
20
+ it('should return false if no store settings are provided', () => {
21
+ const result = paginationUtils.isEnabledInStore({
22
+ ctx: { rootGetters: mockRootGetters },
23
+ storeSettings: undefined as unknown as PaginationSettingsStore,
24
+ enabledFor: { store: 'cluster' }
25
+ });
26
+
27
+ expect(result).toBe(false);
28
+ });
29
+
30
+ it('should return true if no specific resource is being checked', () => {
31
+ const storeSettings: PaginationSettingsStore = { resources: {} };
32
+ const result = paginationUtils.isEnabledInStore({
33
+ ctx: { rootGetters: mockRootGetters },
34
+ storeSettings,
35
+ enabledFor: { store: 'cluster' }
36
+ });
37
+
38
+ expect(result).toBe(true);
39
+ });
40
+
41
+ it('should return true if enableAll is true for the store', () => {
42
+ const storeSettings: PaginationSettingsStore = { resources: { enableAll: true } };
43
+ const result = paginationUtils.isEnabledInStore({
44
+ ctx: { rootGetters: mockRootGetters },
45
+ storeSettings,
46
+ enabledFor: { store: 'cluster', resource: { id: 'pod' } }
47
+ });
48
+
49
+ expect(result).toBe(true);
50
+ });
51
+
52
+ it('should return false if a resource is checked but has no id', () => {
53
+ const storeSettings: PaginationSettingsStore = { resources: {} };
54
+ const result = paginationUtils.isEnabledInStore({
55
+ ctx: { rootGetters: mockRootGetters },
56
+ storeSettings,
57
+ enabledFor: { store: 'cluster', resource: { id: undefined as unknown as string } }
58
+ });
59
+
60
+ expect(result).toBe(false);
61
+ });
62
+
63
+ it('should return true for a generic resource when generic is enabled', () => {
64
+ mockRootGetters['type-map/configuredHeaders'].mockReturnValue(false);
65
+ mockRootGetters['type-map/configuredPaginationHeaders'].mockReturnValue(false);
66
+ mockRootGetters['type-map/hasCustomList'].mockReturnValue(false);
67
+
68
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { generic: true } } };
69
+ const result = paginationUtils.isEnabledInStore({
70
+ ctx: { rootGetters: mockRootGetters },
71
+ storeSettings,
72
+ enabledFor: { store: 'cluster', resource: { id: 'pod' } }
73
+ });
74
+
75
+ expect(result).toBe(true);
76
+ });
77
+
78
+ it('should return false for a non-generic resource when only generic is enabled', () => {
79
+ mockRootGetters['type-map/hasCustomList'].mockReturnValue(true);
80
+
81
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { generic: true } } };
82
+ const result = paginationUtils.isEnabledInStore({
83
+ ctx: { rootGetters: mockRootGetters },
84
+ storeSettings,
85
+ enabledFor: { store: 'cluster', resource: { id: 'pod' } }
86
+ });
87
+
88
+ expect(result).toBe(false);
89
+ });
90
+
91
+ it('should return true if resource id is in enabled list as a string', () => {
92
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { enabled: ['pod'] } } };
93
+ const result = paginationUtils.isEnabledInStore({
94
+ ctx: { rootGetters: mockRootGetters },
95
+ storeSettings,
96
+ enabledFor: { store: 'cluster', resource: { id: 'pod' } }
97
+ });
98
+
99
+ expect(result).toBe(true);
100
+ });
101
+
102
+ it('should return true if resource id is in enabled list as an object without context', () => {
103
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { enabled: [{ resource: 'pod' }] } } };
104
+ const result = paginationUtils.isEnabledInStore({
105
+ ctx: { rootGetters: mockRootGetters },
106
+ storeSettings,
107
+ enabledFor: { store: 'cluster', resource: { id: 'pod' } }
108
+ });
109
+
110
+ expect(result).toBe(true);
111
+ });
112
+
113
+ it('should return false if resource id is in enabled list as an object with empty context', () => {
114
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { enabled: [{ resource: 'pod', context: [] }] } } };
115
+ const result = paginationUtils.isEnabledInStore({
116
+ ctx: { rootGetters: mockRootGetters },
117
+ storeSettings,
118
+ enabledFor: { store: 'cluster', resource: { id: 'pod' } }
119
+ });
120
+
121
+ expect(result).toBe(false);
122
+ });
123
+
124
+ it('should return true if resource id and context match an enabled setting', () => {
125
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { enabled: [{ resource: 'pod', context: ['list'] }] } } };
126
+ const result = paginationUtils.isEnabledInStore({
127
+ ctx: { rootGetters: mockRootGetters },
128
+ storeSettings,
129
+ enabledFor: { store: 'cluster', resource: { id: 'pod', context: 'list' } }
130
+ });
131
+
132
+ expect(result).toBe(true);
133
+ });
134
+
135
+ it('should return false if resource context does not match enabled setting', () => {
136
+ const storeSettings: PaginationSettingsStore = { resources: { enableSome: { enabled: [{ resource: 'pod', context: ['detail'] }] } } };
137
+ const result = paginationUtils.isEnabledInStore({
138
+ ctx: { rootGetters: mockRootGetters },
139
+ storeSettings,
140
+ enabledFor: { store: 'cluster', resource: { id: 'pod', context: 'list' } }
141
+ });
142
+
143
+ expect(result).toBe(false);
144
+ });
145
+ });
146
+
147
+ describe('isEnabled', () => {
148
+ let mockRootGetters: any;
149
+ let mockPlugin: any;
150
+ let enabledFor: PaginationResourceContext;
151
+
152
+ beforeEach(() => {
153
+ enabledFor = { store: 'cluster', resource: { id: 'pod' } };
154
+ mockPlugin = { getAll: jest.fn().mockReturnValue({}) };
155
+ mockRootGetters = {
156
+ 'features/get': jest.fn(),
157
+ 'management/byId': jest.fn(),
158
+ 'type-map/configuredHeaders': jest.fn().mockReturnValue(false),
159
+ 'type-map/configuredPaginationHeaders': jest.fn().mockReturnValue(false),
160
+ 'type-map/hasCustomList': jest.fn().mockReturnValue(false),
161
+ };
162
+ });
163
+
164
+ it('should return false if steve cache is disabled', () => {
165
+ mockRootGetters['features/get'].mockImplementation((feature: string) => feature === STEVE_CACHE ? false : undefined);
166
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
167
+
168
+ expect(result).toBe(false);
169
+ });
170
+
171
+ it('should return false if pagination settings are not defined', () => {
172
+ jest.spyOn(paginationUtils, 'getSettings').mockReturnValue(undefined);
173
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
174
+
175
+ expect(result).toBe(false);
176
+
177
+ jest.clearAllMocks();
178
+ });
179
+
180
+ it('should return false if enabledFor is not provided', () => {
181
+ mockRootGetters['features/get'].mockReturnValue(true);
182
+ const settings: PaginationSettings = { useDefaultStores: true };
183
+
184
+ mockRootGetters['management/byId'].mockImplementation((type: string, id: string) => {
185
+ if (type === 'management.cattle.io.setting' && id === SETTING.UI_PERFORMANCE) {
186
+ return { value: JSON.stringify({ serverPagination: settings }) };
187
+ }
188
+
189
+ return null;
190
+ });
191
+
192
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, undefined as unknown as PaginationResourceContext);
193
+
194
+ expect(result).toBe(false);
195
+ });
196
+
197
+ it('should return true if an extension enables the resource', () => {
198
+ mockRootGetters['features/get'].mockReturnValue(true);
199
+ const extensionSettings = { cluster: { resources: { enableAll: true } } };
200
+
201
+ mockPlugin.getAll.mockReturnValue({ [EXT_IDS.SERVER_SIDE_PAGINATION_RESOURCES]: { 'my-ext': () => extensionSettings } });
202
+
203
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
204
+
205
+ expect(result).toBe(true);
206
+ });
207
+
208
+ it('should still return true if an extension enables the resource but core store does not', () => {
209
+ mockRootGetters['features/get'].mockReturnValue(true);
210
+ const extensionSettings = { cluster: { resources: { enableAll: true } } };
211
+ const defaultSettings = { cluster: { resources: { enableAll: false, enableSome: { enabled: [] } } } };
212
+
213
+ mockPlugin.getAll.mockReturnValue({ [EXT_IDS.SERVER_SIDE_PAGINATION_RESOURCES]: { 'my-ext': () => extensionSettings } });
214
+
215
+ // Mocking PAGINATION_SETTINGS_STORE_DEFAULTS behavior
216
+ jest.spyOn(paginationUtils, 'getStoreDefault').mockReturnValue(defaultSettings);
217
+
218
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
219
+
220
+ expect(result).toBe(true);
221
+ });
222
+
223
+ it('should use default store settings and enable the resource', () => {
224
+ mockRootGetters['features/get'].mockReturnValue(true);
225
+ const settings: PaginationSettings = { useDefaultStores: true };
226
+
227
+ mockRootGetters['management/byId'].mockImplementation((type: string, id: string) => {
228
+ if (type === 'management.cattle.io.setting' && id === SETTING.UI_PERFORMANCE) {
229
+ return { value: JSON.stringify({ serverPagination: settings }) };
230
+ }
231
+
232
+ return null;
233
+ });
234
+
235
+ // Mocking PAGINATION_SETTINGS_STORE_DEFAULTS behavior
236
+ jest.spyOn(paginationUtils, 'getStoreDefault').mockReturnValue({ cluster: { resources: { enableAll: true } } });
237
+
238
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
239
+
240
+ expect(result).toBe(true);
241
+ });
242
+
243
+ it('should use custom store settings and enable the resource', () => {
244
+ mockRootGetters['features/get'].mockReturnValue(true);
245
+ const settings: PaginationSettings = {
246
+ useDefaultStores: false,
247
+ stores: { cluster: { resources: { enableSome: { enabled: ['pod'] } } } }
248
+ };
249
+
250
+ mockRootGetters['management/byId'].mockImplementation((type: string, id: string) => {
251
+ if (type === 'management.cattle.io.setting' && id === SETTING.UI_PERFORMANCE) {
252
+ return { value: JSON.stringify({ serverPagination: settings }) };
253
+ }
254
+
255
+ return null;
256
+ });
257
+
258
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
259
+
260
+ expect(result).toBe(true);
261
+ });
262
+
263
+ it('should return false if neither extension nor main settings enable the resource', () => {
264
+ mockRootGetters['features/get'].mockReturnValue(true);
265
+ const settings: PaginationSettings = {
266
+ useDefaultStores: false,
267
+ stores: { cluster: { resources: { enableSome: { enabled: ['service'] } } } }
268
+ };
269
+
270
+ mockRootGetters['management/byId'].mockImplementation((type: string, id: string) => {
271
+ if (type === 'management.cattle.io.setting' && id === SETTING.UI_PERFORMANCE) {
272
+ return { value: JSON.stringify({ serverPagination: settings }) };
273
+ }
274
+
275
+ return null;
276
+ });
277
+
278
+ const result = paginationUtils.isEnabled({ rootGetters: mockRootGetters, $plugin: mockPlugin }, enabledFor);
279
+
280
+ expect(result).toBe(false);
281
+ });
282
+ });
283
+ });
@@ -116,6 +116,10 @@ describe('formRules', () => {
116
116
  ['git@github.com:rancher/dashboard/%20', undefined],
117
117
  ['git@git.apps.local:fleet/fleet-local.git', undefined],
118
118
  ['git@git.apps.local:33333/fleet/fleet-local.git', undefined],
119
+ ['ssh://git@github.com:rancher/dashboard', undefined],
120
+ ['ssh://git@github.com:rancher/dashboard/', undefined],
121
+ ['ssh://git@git.apps.local:fleet/fleet-local.git', undefined],
122
+ ['ssh://git@git.apps.local:33333/fleet/fleet-local.git', undefined],
119
123
 
120
124
  // Not valid HTTP(s)
121
125
  ['https://github.com/rancher/ dashboard.git', message],
@@ -145,6 +149,9 @@ describe('formRules', () => {
145
149
  ['git@git.apps.local:/fleet/fleet-local.git', message],
146
150
  ['git@.git', message],
147
151
  ['git@', message],
152
+ ['ssh://git@github.com:/rancher/dashboard.git ', message],
153
+ ['ssh://git@github.com/rancher/ dashboard.git', message],
154
+ ['ssh://git@github.com/rancher/ dashboard', message],
148
155
 
149
156
  [undefined, message],
150
157
  ['', message]
@@ -200,8 +200,8 @@ export default function(
200
200
  return message;
201
201
  }
202
202
 
203
- // Test http(s) protocol
204
- if (protocol && (!/^(http|http(s))/gm.test(protocol) || (!url.startsWith('https://') && !url.startsWith('http://')))) {
203
+ // Test http(s)/ssh protocol
204
+ if (protocol && (!/^(http|https|ssh)$/gm.test(protocol) || (!url.startsWith('https://') && !url.startsWith('http://') && !url.startsWith('ssh://')))) {
205
205
  return message;
206
206
  }
207
207