@rancher/shell 3.0.12-rc.5 → 3.0.12-rc.6

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 (89) hide show
  1. package/apis/intf/resources-api/resource-base.ts +42 -2
  2. package/apis/intf/resources-api/resource-instance.ts +101 -0
  3. package/apis/intf/resources-api/resources-api.ts +177 -23
  4. package/apis/intf/resources.ts +8 -6
  5. package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
  6. package/apis/resources/resources-api-class.ts +232 -43
  7. package/assets/images/providers/traefik.png +0 -0
  8. package/assets/translations/en-us.yaml +10 -4
  9. package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
  10. package/chart/rancher-monitoring-dashboards/index.vue +155 -0
  11. package/cloud-credential/__tests__/generic.test.ts +132 -0
  12. package/cloud-credential/generic.vue +33 -3
  13. package/components/EtcdInfoBanner.vue +2 -12
  14. package/components/GrafanaDashboard.vue +2 -8
  15. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
  16. package/components/Resource/Detail/Masthead/index.vue +8 -3
  17. package/components/form/Security.vue +1 -1
  18. package/components/formatter/Translate.vue +22 -1
  19. package/components/nav/Group.vue +5 -10
  20. package/components/nav/TopLevelMenu.vue +1 -30
  21. package/components/nav/__tests__/Group.test.ts +61 -0
  22. package/config/labels-annotations.js +1 -0
  23. package/config/table-headers.js +5 -2
  24. package/core/__tests__/plugin-products-apply.test.ts +787 -0
  25. package/core/__tests__/plugin-products-extend.test.ts +189 -0
  26. package/core/__tests__/plugin-products-helpers.test.ts +81 -78
  27. package/core/__tests__/plugin-products-new.test.ts +389 -0
  28. package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
  29. package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
  30. package/core/plugin-products-base.ts +201 -93
  31. package/core/plugin-products-extending.ts +1 -1
  32. package/core/plugin-products-external.ts +380 -0
  33. package/core/plugin-products-helpers.ts +28 -24
  34. package/core/plugin-products-internal.ts +207 -0
  35. package/core/plugin-products-top-level.ts +10 -5
  36. package/core/plugin-products-type-guards.ts +17 -6
  37. package/core/plugin-products.ts +5 -7
  38. package/core/plugin-types.ts +1 -389
  39. package/core/plugin.ts +10 -11
  40. package/core/types.ts +6 -202
  41. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
  42. package/detail/pod.vue +1 -1
  43. package/detail/provisioning.cattle.io.cluster.vue +0 -2
  44. package/edit/auth/AuthProviderWarningBanners.vue +12 -0
  45. package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
  46. package/edit/auth/__tests__/azuread.test.ts +1 -0
  47. package/edit/auth/__tests__/github.test.ts +1 -0
  48. package/edit/auth/__tests__/oidc.test.ts +1 -0
  49. package/edit/auth/__tests__/saml.test.ts +1 -0
  50. package/edit/auth/ldap/__tests__/index.test.ts +1 -0
  51. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  52. package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
  53. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
  54. package/list/management.cattle.io.user.vue +24 -5
  55. package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
  56. package/machine-config/__tests__/generic.test.ts +163 -0
  57. package/machine-config/components/EC2Networking.vue +31 -7
  58. package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
  59. package/machine-config/generic.vue +32 -5
  60. package/mixins/resource-fetch.js +1 -1
  61. package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
  62. package/models/pod.js +12 -10
  63. package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
  64. package/package.json +1 -1
  65. package/pages/auth/login.vue +1 -1
  66. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
  67. package/pages/c/_cluster/apps/charts/install.vue +29 -0
  68. package/pages/c/_cluster/auth/config/index.vue +11 -4
  69. package/pages/c/_cluster/monitoring/index.vue +6 -1
  70. package/plugins/dashboard-store/resource-class.js +10 -3
  71. package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
  72. package/plugins/steve/steve-class.js +43 -0
  73. package/plugins/steve/subscribe.js +11 -0
  74. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
  75. package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
  76. package/store/__tests__/auth.test.ts +18 -0
  77. package/store/__tests__/notifications.test.ts +466 -1
  78. package/store/__tests__/ui-context.test.ts +255 -0
  79. package/store/auth.js +11 -1
  80. package/store/features.js +1 -0
  81. package/store/plugins.js +6 -0
  82. package/types/rancher/steve.api.ts +2 -2
  83. package/types/shell/index.d.ts +58 -0
  84. package/types/store/dashboard-store.types.ts +2 -2
  85. package/types/store/type-map.ts +262 -1
  86. package/utils/async.ts +2 -0
  87. package/utils/grafana.js +2 -17
  88. package/utils/monitoring.js +38 -1
  89. package/core/__tests__/plugin-products.test.ts +0 -4694
package/core/types.ts CHANGED
@@ -1,13 +1,9 @@
1
- import { ProductFunction } from './plugin';
2
1
  import { RouteRecordRaw } from 'vue-router';
3
2
  import type { ExtensionManager } from '@shell/types/extension-manager';
4
3
  import { PaginationSettingsStores } from '@shell/types/resources/settings';
5
- import type {
6
- ProductMetadata, ProductSinglePage,
7
- StandardProductName, RouteRecordRawWithParams, ProductChild,
8
- ProductChildGroup,
9
- ProductChildPage
10
- } from './plugin-types';
4
+ import { IExtensionProducts } from '@shell/core/plugin-products-external';
5
+ import { RouteRecordRawWithParams } from '@shell/core/plugin-types';
6
+ import { TypeMapProduct } from '@shell/types/store/type-map';
11
7
 
12
8
  // Cluster Provisioning types
13
9
  export * from './types-provisioning';
@@ -200,137 +196,6 @@ export type ExtensionEnvironment = {
200
196
  docsVersion: string; /** e.g. 'v2.10' */
201
197
  };
202
198
 
203
- export interface ProductOptions {
204
- /**
205
- * The category this product belongs under. i.e. 'config'
206
- */
207
- category?: string;
208
-
209
- /**
210
- * Hide the Copy KubeConfig button in the header
211
- */
212
- hideCopyConfig?: boolean;
213
-
214
- /**
215
- * Hide the Download KubeConfig button in the header
216
- */
217
- hideKubeConfig?: boolean;
218
-
219
- /**
220
- * Hide the Kubectl Shell button in the header
221
- */
222
- hideKubeShell?: boolean;
223
-
224
- /**
225
- * Hide the Namespace location
226
- */
227
- hideNamespaceLocation?: boolean;
228
-
229
- /**
230
- * Hide the system resources
231
- */
232
-
233
- hideSystemResources?: boolean;
234
- /**
235
- * The icon that should be displayed beside this item in the navigation.
236
- */
237
- icon?: string,
238
-
239
- /**
240
- * Only load the product if the feature is present
241
- */
242
- ifFeature?: string | RegExp;
243
-
244
- /**
245
- * Only load the product if the type is present
246
- */
247
- ifHave?: string;
248
-
249
- /**
250
- * Only load the product if the group is present
251
- */
252
- ifHaveGroup?: string | RegExp;
253
-
254
- /**
255
- * Only load the product if the type is present
256
- */
257
- ifHaveType?: string | RegExp;
258
-
259
- /**
260
- * Hide the product if the type is present (opposite of ifHaveType)
261
- */
262
- ifNotHaveType?: string | RegExp;
263
-
264
- /**
265
- * The vuex store that this product should use by default i.e. 'management'
266
- */
267
- inStore?: string;
268
-
269
- /**
270
- * Show the cluster switcher in the navigation
271
- */
272
- showClusterSwitcher?: boolean;
273
-
274
- /**
275
- * Indicates whether UI Extensions can add pages to this product
276
- */
277
- extendable?: boolean;
278
-
279
- /**
280
- * Show the namespace filter in the header
281
- */
282
- showNamespaceFilter?: boolean;
283
-
284
- /**
285
- * A number used to determine where in navigation this item will be placed. The highest number will be at the top of the list.
286
- */
287
- weight?: number;
288
-
289
- /**
290
- * The route that the product will lead to if click on in navigation.
291
- */
292
- to?: PluginRouteRecordRaw;
293
-
294
- /**
295
- * Whether the product can be removed by users (default: false — products are built-in/not removable unless explicitly set to true)
296
- */
297
- removable?: boolean;
298
-
299
- /**
300
- * Alternative to the icon property. Uses require
301
- */
302
- svg?: Function;
303
-
304
- /**
305
- * Product name
306
- */
307
- name?: string;
308
-
309
- /**
310
- * controls whether a workspace switcher dropdown appears in the header (instead of the namespace filter) if set to true
311
- */
312
- showWorkspaceSwitcher?: boolean;
313
-
314
- /**
315
- *
316
- */
317
- label?: string;
318
-
319
- labelKey?: string;
320
-
321
- iconHeader?: string;
322
-
323
- // Do not use - internal use only
324
- version?: number;
325
-
326
- /**
327
- * Leaving these here for completeness but I don't think these should be advertised as useable to plugin creators.
328
- */
329
- // ifHaveVerb: string | RegExp;
330
- // supportRoute: string;
331
- // typeStoreMap: string;
332
- }
333
-
334
199
  /**
335
200
  * Configuration required to show a header in a ResourceTable
336
201
  */
@@ -399,17 +264,6 @@ export interface HeaderOptions {
399
264
  */
400
265
  export type PaginationHeaderOptions = Omit<HeaderOptions, 'getValue'>
401
266
 
402
- export type ResourceTypeConfig = {
403
- options?: {
404
- isCreatable?: boolean;
405
- isEditable?: boolean;
406
- },
407
- listHeaders?: {
408
- legacy?: HeaderOptions[];
409
- paginated?: PaginationHeaderOptions[];
410
- }
411
- };
412
-
413
267
  /**
414
268
  * External extension configuration for @HeaderOptions
415
269
  */
@@ -568,7 +422,7 @@ export interface DSLReturnType {
568
422
  * @param options {@link ProductOptions}
569
423
  * @returns {@link void}
570
424
  */
571
- product: (options: ProductOptions) => void;
425
+ product: (options: TypeMapProduct) => void;
572
426
 
573
427
  /**
574
428
  /**
@@ -691,62 +545,12 @@ export type ModelExtensionContext = {
691
545
  /**
692
546
  * Constructor signature for a model extension
693
547
  */
694
- export type ModelExtensionConstructor = (context: ModelExtensionContext) => Object;
548
+ export type ModelExtensionConstructor = new (context: ModelExtensionContext) => Object;
695
549
 
696
550
  /**
697
551
  * Interface for a UI Extension
698
552
  */
699
- export interface IExtension {
700
- /**
701
- * Register a top-level product as a flag on the plugin
702
- * @internal - DO NOT USE - Internal API only
703
- */
704
- _registerTopLevelProduct(): void;
705
- /**
706
- *
707
- * @internal - DO NOT USE - Internal API only
708
- */
709
- _setStartRouteWithProduct(value: boolean): void;
710
-
711
- /**
712
- * Add a product to the sidebar, with children and a side menu for navigation for internal pages
713
- * @param name
714
- * @param config
715
- */
716
- addProduct(product: ProductMetadata, config: ProductChildPage[]): void;
717
- addProduct(product: ProductMetadata, config: ProductChildGroup[]): void;
718
- addProduct(product: ProductMetadata, config: ProductChild[]): void;
719
-
720
- /**
721
- * Add a product to the sidebar, without children (no side menu, single page only)
722
- * @param product
723
- */
724
- addProduct(product: ProductSinglePage): void;
725
-
726
- /**
727
- * Add a product with just a name (convenience/bridge method for quick setup).
728
- * Creates a basic product with an empty page component automatically.
729
- * This is useful for getting started quickly - expand to the full API once you're ready to add custom pages.
730
- * @param productName Simple product name - will be used as both the name and label
731
- */
732
- addProduct(productName: string): void;
733
-
734
- /**
735
- * Add a product to the sidebar (deprecated, use other signatures of addProduct instead)
736
- * @deprecated Use other `addProduct` signatures instead
737
- * @param importFn Function that will import the module containing a product definition
738
- */
739
- addProduct(importFn: ProductFunction): void;
740
-
741
- /**
742
- * Extend an existing product in Rancher, with children and a side menu for navigation for internal pages
743
- *
744
- * @param product Product to be extended
745
- * @param config Product extension configuration
746
- */
747
- extendProduct(product: StandardProductName | string, config: ProductChildPage[]): void;
748
- extendProduct(product: StandardProductName | string, config: ProductChildGroup[]): void;
749
- extendProduct(product: StandardProductName | string, config: ProductChild[]): void;
553
+ export interface IExtension extends IExtensionProducts {
750
554
 
751
555
  /**
752
556
  * Add a locale to the i18n store
@@ -241,4 +241,56 @@ describe('view: provisioning.cattle.io.cluster', () => {
241
241
  expect(wrapper.vm.fakeMachines).toHaveLength(0);
242
242
  });
243
243
  });
244
+
245
+ describe('computed: showLog', () => {
246
+ it('returns true when mgmt has a log link and extDetailTabs.logs is enabled', async() => {
247
+ const value = { mgmt: { hasLink: (link: string) => link === 'log' } };
248
+
249
+ const wrapper = shallowMount(ProvisioningCattleIoCluster, {
250
+ props: { value },
251
+ global: { mocks },
252
+ });
253
+
254
+ await wrapper.setData({ extDetailTabs: { logs: true } });
255
+
256
+ expect(wrapper.vm.showLog).toStrictEqual(true);
257
+ });
258
+
259
+ it('returns false when mgmt does not have a log link', async() => {
260
+ const value = { mgmt: { hasLink: () => false } };
261
+
262
+ const wrapper = shallowMount(ProvisioningCattleIoCluster, {
263
+ props: { value },
264
+ global: { mocks },
265
+ });
266
+
267
+ await wrapper.setData({ extDetailTabs: { logs: true } });
268
+
269
+ expect(wrapper.vm.showLog).toStrictEqual(false);
270
+ });
271
+
272
+ it('returns false when mgmt has a log link but extDetailTabs.logs is disabled', async() => {
273
+ const value = { mgmt: { hasLink: (link: string) => link === 'log' } };
274
+
275
+ const wrapper = shallowMount(ProvisioningCattleIoCluster, {
276
+ props: { value },
277
+ global: { mocks },
278
+ });
279
+
280
+ await wrapper.setData({ extDetailTabs: { logs: false } });
281
+
282
+ expect(wrapper.vm.showLog).toStrictEqual(false);
283
+ });
284
+
285
+ it('returns false when mgmt is undefined', async() => {
286
+ const value = {};
287
+
288
+ const wrapper = shallowMount(ProvisioningCattleIoCluster, {
289
+ props: { value },
290
+ global: { mocks },
291
+ });
292
+
293
+ expect(wrapper.vm.showLog).toBeFalsy();
294
+ });
295
+ });
244
296
  });
package/detail/pod.vue CHANGED
@@ -38,7 +38,7 @@ export default {
38
38
  const projectId = namespace?.metadata?.labels[PROJECT];
39
39
 
40
40
  if (projectId) {
41
- this.POD_PROJECT_METRICS_DETAIL_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-containers-1/rancher-pod-containers?orgId=1'`;
41
+ this.POD_PROJECT_METRICS_DETAIL_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-containers-1/rancher-pod-containers?orgId=1`;
42
42
  this.POD_PROJECT_METRICS_SUMMARY_URL = `/api/v1/namespaces/cattle-project-${ projectId }-monitoring/services/http:cattle-project-${ projectId }-monitoring-grafana:80/proxy/d/rancher-pod-1/rancher-pod?orgId=1`;
43
43
 
44
44
  this.showProjectMetrics = await allDashboardsExist(this.$store, this.currentCluster.id, [this.POD_PROJECT_METRICS_DETAIL_URL, this.POD_PROJECT_METRICS_SUMMARY_URL], 'cluster', projectId);
@@ -227,9 +227,7 @@ export default {
227
227
  if ( this.$store.getters['management/canList'](MANAGEMENT.RKE_TEMPLATE) ) {
228
228
  this.$store.dispatch('management/findAll', { type: MANAGEMENT.RKE_TEMPLATE });
229
229
  }
230
- },
231
230
 
232
- created() {
233
231
  if ( this.showLog ) {
234
232
  this.connectLog();
235
233
  }
@@ -1,12 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent } from 'vue';
3
3
  import { Banner } from '@components/Banner';
4
+ import { HIDE_LOCAL_AUTH_PROVIDER } from '@shell/store/features';
4
5
 
5
6
  export default defineComponent({
6
7
  name: 'AuthProviderWarningBanners',
7
8
 
8
9
  components: { Banner },
9
10
 
11
+ data() {
12
+ return { disableLocalAuth: this.$store.getters['features/get'](HIDE_LOCAL_AUTH_PROVIDER) };
13
+ },
14
+
10
15
  props: {
11
16
  tArgs: {
12
17
  type: Object,
@@ -24,6 +29,13 @@ export default defineComponent({
24
29
  color="warning"
25
30
  data-testid="auth-provider-disabled-warning-banner"
26
31
  />
32
+ <Banner
33
+ v-if="disableLocalAuth"
34
+ color="warning"
35
+ data-testid="auth-provider-disable-local-auth-banner"
36
+ >
37
+ <span v-clean-html="t('authConfig.bannerEnableAuthProvider', {}, true)" />
38
+ </Banner>
27
39
  <Banner
28
40
  color="warning"
29
41
  data-testid="auth-provider-admin-permissions-warning-banner"
@@ -4,7 +4,16 @@ import { mount } from '@vue/test-utils';
4
4
  describe('component: AuthProviderWarningBanners.vue', () => {
5
5
  const wrapper = mount(AuthProviderWarningBanners, {
6
6
  props: { tArgs: { provider: 'Any Provider', username: 'username' } },
7
- global: { mocks: { $store: { getters: { 'i18n/t': (text: string) => text } } } }
7
+ global: {
8
+ mocks: {
9
+ $store: {
10
+ getters: {
11
+ 'i18n/t': (text: string) => text,
12
+ 'features/get': () => false,
13
+ }
14
+ }
15
+ }
16
+ }
8
17
  });
9
18
 
10
19
  it('should render properly', () => {
@@ -52,6 +52,7 @@ const requiredSetup = (modelOverrides = {}) => ({
52
52
  'current_store/all': jest.fn(),
53
53
  'i18n/t': (val: string) => val,
54
54
  'i18n/exists': jest.fn(),
55
+ 'features/get': () => false,
55
56
  },
56
57
  dispatch: jest.fn()
57
58
  },
@@ -56,6 +56,7 @@ const requiredSetup = (modelOverrides = {}, dataOverrides = {}) => ({
56
56
  'current_store/all': jest.fn(),
57
57
  'i18n/t': (val: string) => val,
58
58
  'i18n/exists': jest.fn(),
59
+ 'features/get': () => false,
59
60
  },
60
61
  dispatch: jest.fn(),
61
62
  },
@@ -69,6 +69,7 @@ describe('oidc.vue', () => {
69
69
  'current_store/all': jest.fn(),
70
70
  'i18n/t': (val: string) => val,
71
71
  'i18n/exists': jest.fn(),
72
+ 'features/get': () => false,
72
73
  },
73
74
  dispatch: jest.fn()
74
75
  },
@@ -63,6 +63,7 @@ const mountOptions = (model: object) => ({
63
63
  'current_store/all': jest.fn(),
64
64
  'i18n/t': (val: string) => val,
65
65
  'i18n/exists': jest.fn(),
66
+ 'features/get': () => false,
66
67
  },
67
68
  dispatch: jest.fn(),
68
69
  },
@@ -59,6 +59,7 @@ const buildSetup = (type = 'openldap', modelOverride = {}, localDataOverride = {
59
59
  'current_store/all': jest.fn(),
60
60
  'i18n/t': (val: string) => val,
61
61
  'i18n/exists': jest.fn(),
62
+ 'features/get': () => false,
62
63
  },
63
64
  dispatch: jest.fn()
64
65
  },
@@ -87,7 +87,7 @@ export default {
87
87
  await this.$store.dispatch('management/watch', { type: MANAGEMENT.CLUSTER, registerType: true });
88
88
  await this.$store.dispatch('management/watch', { type: CAPI.RANCHER_CLUSTER, registerType: true });
89
89
  } else {
90
- hash.mgmtClusters = this.value.waitForMgmt();
90
+ hash.mgmtCluster = this.value.waitForMgmt();
91
91
  }
92
92
 
93
93
  // No need to fetch charts when editing an RKE1 cluster
@@ -1629,7 +1629,7 @@ export default {
1629
1629
  const isIpv6 = this.hasOnlyIpv6Pools;
1630
1630
 
1631
1631
  const flannelMasqInvalid = isIpv6 && isK3s && !flannelMasqEnabled;
1632
- const stackPrefInvalid = (isIpv6 && stackPreference !== STACK_PREFS.IPV6) || (isDualStack && ![STACK_PREFS.IPV6, STACK_PREFS.DUAL].includes(stackPreference));
1632
+ const stackPrefInvalid = (isIpv6 && stackPreference !== STACK_PREFS.IPV6) || (isDualStack && stackPreference !== STACK_PREFS.DUAL);
1633
1633
 
1634
1634
  const clusterCIDRInvalid = (isIpv6 || isDualStack) && !clusterCIDR.includes(':');
1635
1635
  const serviceCIDRInvalid = (isIpv6 || isDualStack) && !serviceCIDR.includes(':');
@@ -120,7 +120,7 @@ export default {
120
120
 
121
121
  stackPreference: {
122
122
  get() {
123
- return this.localValue.spec?.networking?.stackPreference || STACK_PREFS.IPV4;
123
+ return this.localValue.spec?.networking?.stackPreference || STACK_PREFS.DUAL;
124
124
  },
125
125
  set(neu) {
126
126
  if (!this.localValue.spec.networking) {
@@ -295,7 +295,7 @@ export default {
295
295
  <div class="col span-3">
296
296
  <LabeledSelect
297
297
  :key="hasSomeIpv6Pools"
298
- :value="localValue?.spec?.rkeConfig?.networking?.stackPreference || STACK_PREFS.IPV4"
298
+ :value="localValue?.spec?.rkeConfig?.networking?.stackPreference || STACK_PREFS.DUAL"
299
299
  :mode="mode"
300
300
  :options="stackPreferenceOptions"
301
301
  data-testid="network-tab-stackpreferences"
@@ -8,6 +8,8 @@ import ResourceFetch from '@shell/mixins/resource-fetch';
8
8
  import { isAdminUser } from '@shell/store/type-map';
9
9
  import TableDataUserIcon from '@shell/components/TableDataUserIcon';
10
10
  import { RcButton } from '@components/RcButton';
11
+ import { allHash } from '@shell/utils/promise';
12
+ import Loading from '@shell/components/Loading.vue';
11
13
 
12
14
  export default {
13
15
  components: {
@@ -15,7 +17,8 @@ export default {
15
17
  ResourceTable,
16
18
  Masthead,
17
19
  TableDataUserIcon,
18
- RcButton
20
+ RcButton,
21
+ Loading
19
22
  },
20
23
  mixins: [ResourceFetch],
21
24
  props: {
@@ -40,9 +43,16 @@ export default {
40
43
  }
41
44
  },
42
45
  async fetch() {
43
- await this.$fetchType(this.resource);
46
+ const promises = {
47
+ resources: this.$fetchType(this.resource),
48
+ localProviderEnabled: this.$store.dispatch('auth/getLocalProviderEnabled'),
49
+ membershipRefreshRequests: this.$store.dispatch('management/create', { type: EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS }),
50
+ };
51
+
52
+ const res = await allHash(promises);
44
53
 
45
- this.membershipRefreshRequests = await this.$store.dispatch('management/create', { type: EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS });
54
+ this.localProviderEnabled = res.localProviderEnabled;
55
+ this.membershipRefreshRequests = res.membershipRefreshRequests;
46
56
  this.canRefreshMemberships = !!this.membershipRefreshRequests?.canRefreshMemberships;
47
57
  },
48
58
 
@@ -87,7 +97,14 @@ export default {
87
97
 
88
98
  isAdmin() {
89
99
  return isAdminUser(this.$store.getters);
90
- }
100
+ },
101
+
102
+ canCreateUsers() {
103
+ const userCan = !!this.schema?.collectionMethods?.find((x) => x.toLowerCase() === 'post');
104
+ const systemCan = this.localProviderEnabled;
105
+
106
+ return userCan && systemCan;
107
+ },
91
108
  },
92
109
 
93
110
  methods: {
@@ -107,13 +124,15 @@ export default {
107
124
  </script>
108
125
 
109
126
  <template>
110
- <div>
127
+ <Loading v-if="$fetchState.pending" />
128
+ <div v-else>
111
129
  <Masthead
112
130
  :schema="schema"
113
131
  :resource="resource"
114
132
  :show-incremental-loading-indicator="incrementalLoadingIndicator"
115
133
  :load-resources="loadResources"
116
134
  :load-indeterminate="loadIndeterminate"
135
+ :is-creatable="canCreateUsers"
117
136
  >
118
137
  <template #extraActions>
119
138
  <AsyncButton
@@ -31,7 +31,7 @@ class ManagementClusterUtils {
31
31
  }
32
32
 
33
33
  const existingFilters = pagination.filters;
34
- const requiredFilters = paginationFilterClusters($store, false);
34
+ const requiredFilters = paginationFilterClusters($store);
35
35
 
36
36
  for (let i = 0; i < requiredFilters.length; i++) {
37
37
  let found = false;