@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.
- package/assets/images/pl/dark/rancher-logo.svg +131 -44
- package/assets/images/pl/rancher-logo.svg +120 -44
- package/assets/styles/base/_basic.scss +2 -2
- package/assets/styles/base/_color-classic.scss +51 -0
- package/assets/styles/base/_color.scss +3 -3
- package/assets/styles/base/_mixins.scss +1 -1
- package/assets/styles/base/_variables-classic.scss +47 -0
- package/assets/styles/global/_button.scss +49 -17
- package/assets/styles/global/_form.scss +1 -1
- package/assets/styles/themes/_dark.scss +4 -0
- package/assets/styles/themes/_light.scss +3 -69
- package/assets/styles/themes/_modern.scss +194 -50
- package/assets/styles/vendor/vue-select.scss +1 -2
- package/assets/translations/en-us.yaml +33 -21
- package/components/ClusterIconMenu.vue +1 -1
- package/components/ClusterProviderIcon.vue +1 -1
- package/components/CodeMirror.vue +1 -1
- package/components/IconOrSvg.vue +40 -29
- package/components/ResourceDetail/index.vue +1 -0
- package/components/SortableTable/sorting.js +3 -1
- package/components/Tabbed/index.vue +5 -5
- package/components/form/ResourceTabs/index.vue +37 -18
- package/components/form/SecretSelector.vue +6 -2
- package/components/nav/Group.vue +29 -9
- package/components/nav/Header.vue +6 -8
- package/components/nav/NamespaceFilter.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +47 -20
- package/components/nav/TopLevelMenu.vue +44 -14
- package/components/nav/Type.vue +0 -5
- package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
- package/config/pagination-table-headers.js +10 -2
- package/config/product/explorer.js +4 -3
- package/config/table-headers.js +9 -0
- package/core/plugin.ts +18 -6
- package/core/types.ts +8 -0
- package/detail/provisioning.cattle.io.cluster.vue +1 -0
- package/dialog/InstallExtensionDialog.vue +71 -45
- package/dialog/UninstallExtensionDialog.vue +2 -1
- package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
- package/edit/auth/oidc.vue +86 -16
- package/mixins/__tests__/chart.test.ts +1 -1
- package/mixins/chart.js +1 -1
- package/models/event.js +7 -0
- package/models/provisioning.cattle.io.cluster.js +9 -0
- package/package.json +1 -1
- package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
- package/pages/c/_cluster/settings/performance.vue +1 -1
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
- package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
- package/pages/c/_cluster/uiplugins/index.vue +110 -94
- package/plugins/__tests__/subscribe.events.test.ts +194 -0
- package/plugins/dashboard-store/actions.js +3 -0
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/dashboard-store/resource-class.js +3 -3
- package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
- package/plugins/steve/index.js +18 -10
- package/plugins/steve/mutations.js +2 -2
- package/plugins/steve/resourceWatcher.js +2 -2
- package/plugins/steve/steve-pagination-utils.ts +12 -9
- package/plugins/steve/subscribe.js +113 -85
- package/plugins/subscribe-events.ts +211 -0
- package/rancher-components/BadgeState/BadgeState.vue +8 -6
- package/rancher-components/Banner/Banner.vue +2 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
- package/rancher-components/Form/Radio/RadioButton.vue +3 -3
- package/store/index.js +12 -22
- package/types/extension-manager.ts +8 -1
- package/types/resources/settings.d.ts +24 -17
- package/types/shell/index.d.ts +352 -335
- package/types/store/subscribe-events.types.ts +70 -0
- package/types/store/subscribe.types.ts +6 -22
- package/utils/pagination-utils.ts +87 -28
- package/utils/pagination-wrapper.ts +6 -8
- package/utils/sort.js +5 -0
- package/utils/unit-tests/pagination-utils.spec.ts +283 -0
- package/utils/validators/formRules/__tests__/index.test.ts +7 -0
- 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
|
|
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):
|
|
43
|
-
public getStoreSettings(serverPagination: PaginationSettings):
|
|
44
|
-
public getStoreSettings(arg: any | PaginationSettings):
|
|
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():
|
|
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
|
-
*
|
|
89
|
+
* Helper - check if a specific resource in a specific store is enabled given the provided settings
|
|
86
90
|
*/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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|
|
|
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
|
|