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

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 (81) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/translations/en-us.yaml +39 -10
  3. package/components/ActionDropdownShell.vue +5 -3
  4. package/components/ButtonGroup.vue +26 -1
  5. package/components/CruResource.vue +51 -2
  6. package/components/PromptRestore.vue +93 -32
  7. package/components/Questions/index.vue +1 -0
  8. package/components/ResourceTable.vue +1 -0
  9. package/components/SortableTable/index.vue +4 -3
  10. package/components/Wizard.vue +14 -1
  11. package/components/__tests__/ButtonGroup.test.ts +56 -0
  12. package/components/__tests__/PromptRestore.test.ts +169 -19
  13. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  14. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  15. package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
  16. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  17. package/components/form/FileSelector.vue +39 -1
  18. package/components/form/PrivateRegistry.constants.ts +7 -0
  19. package/components/form/PrivateRegistry.vue +253 -18
  20. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  21. package/components/form/__tests__/FileSelector.test.ts +23 -0
  22. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  23. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  24. package/components/formatter/EtcdSnapshotName.vue +73 -0
  25. package/components/nav/Header.vue +8 -1
  26. package/components/templates/default.vue +7 -0
  27. package/config/features.js +1 -0
  28. package/config/labels-annotations.js +2 -0
  29. package/config/product/manager.js +6 -0
  30. package/config/secret.ts +10 -0
  31. package/config/settings.ts +6 -2
  32. package/config/types.js +7 -0
  33. package/detail/provisioning.cattle.io.cluster.vue +79 -3
  34. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  35. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  36. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  37. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
  38. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  39. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  40. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  41. package/edit/fleet.cattle.io.helmop.vue +51 -5
  42. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  43. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  44. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  45. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  46. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  47. package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
  48. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  49. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  50. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
  51. package/models/__tests__/secret.test.ts +68 -1
  52. package/models/management.cattle.io.cluster.js +21 -3
  53. package/models/pod.js +13 -2
  54. package/models/provisioning.cattle.io.cluster.js +59 -9
  55. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  56. package/models/secret.js +19 -0
  57. package/models/workload.js +12 -7
  58. package/package.json +1 -1
  59. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  60. package/pages/c/_cluster/apps/charts/install.vue +114 -28
  61. package/pkg/require-asset.lib.js +25 -0
  62. package/pkg/vue.config.js +7 -0
  63. package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
  64. package/plugins/dashboard-store/getters.js +0 -1
  65. package/plugins/dashboard-store/resource-class.js +52 -12
  66. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  67. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  68. package/rancher-components/RcButton/index.ts +1 -1
  69. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  70. package/store/__tests__/features.test.ts +131 -0
  71. package/store/__tests__/growl.test.ts +374 -0
  72. package/store/__tests__/modal.test.ts +131 -0
  73. package/store/__tests__/slideInPanel.test.ts +88 -0
  74. package/store/__tests__/type-map.utils.test.ts +433 -0
  75. package/store/features.js +4 -0
  76. package/types/shell/index.d.ts +62 -0
  77. package/utils/__tests__/operation-cr.test.ts +34 -0
  78. package/utils/operation-cr.js +19 -0
  79. package/utils/require-asset.ts +7 -0
  80. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  81. package/utils/validators/private-registry.ts +15 -4
@@ -0,0 +1,34 @@
1
+ import { createOperationCR } from '@shell/utils/operation-cr';
2
+
3
+ describe('util: operation-cr', () => {
4
+ it('should dispatch create and save the operation resource', async() => {
5
+ const save = jest.fn().mockResolvedValue({ id: 'op-1' });
6
+ const dispatch = jest.fn().mockResolvedValue({ save });
7
+ const spec = {
8
+ clusterRef: {
9
+ apiVersion: 'management.cattle.io/v3',
10
+ kind: 'Cluster',
11
+ name: 'c-m-1',
12
+ }
13
+ };
14
+
15
+ const out = await createOperationCR(dispatch, 'operation.test', spec, 'c-m-1', 'cluster-name');
16
+
17
+ expect(dispatch).toHaveBeenCalledWith('management/create', {
18
+ type: 'operation.test',
19
+ metadata: {
20
+ namespace: 'c-m-1',
21
+ generateName: 'cluster-name-'
22
+ },
23
+ spec,
24
+ }, { root: true });
25
+ expect(save).toHaveBeenCalledWith();
26
+ expect(out).toStrictEqual({ id: 'op-1' });
27
+ });
28
+
29
+ it('should surface create failures', async() => {
30
+ const dispatch = jest.fn().mockRejectedValue(new Error('forbidden'));
31
+
32
+ await expect(createOperationCR(dispatch, 'operation.test', {}, 'c-m-1', 'cluster-name')).rejects.toThrow('forbidden');
33
+ });
34
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Create a day 2 operation CR for imported clusters.
3
+ *
4
+ * @param {Function} dispatch - The Vuex dispatch function
5
+ * @param {string} type - The operation CRD type
6
+ * @param {object} spec - The operation spec
7
+ * @param {string} namespace - The namespace for the operation CR
8
+ * @param {string} namePrefix - The name prefix for the generated name
9
+ * @returns {Promise} The saved resource
10
+ */
11
+ export async function createOperationCR(dispatch, type, spec, namespace, namePrefix) {
12
+ const resource = await dispatch('management/create', {
13
+ type,
14
+ metadata: { namespace, generateName: `${ namePrefix }-` },
15
+ spec,
16
+ }, { root: true });
17
+
18
+ return resource.save();
19
+ }
@@ -88,6 +88,13 @@ export function requireJson(path: string): object {
88
88
  return mod.default || mod;
89
89
  }
90
90
 
91
+ // Expose asset resolvers on window so extension builds (which use a stub
92
+ // instead of require.context) can delegate to the host dashboard at runtime.
93
+ if (typeof window !== 'undefined') {
94
+ (window as any).__shell_requireAsset = requireAsset;
95
+ (window as any).__shell_requireJson = requireJson;
96
+ }
97
+
91
98
  // Exported for testing — allows injecting mock contexts
92
99
  export function _setContexts(img: WebpackRequireContext | null, json: WebpackRequireContext | null): void {
93
100
  imgCtx = img;
@@ -3,28 +3,22 @@ import { privateRegistryRequired } from '@shell/utils/validators/private-registr
3
3
  const makeCtx = (overrides: any = {}) => ({
4
4
  t: jest.fn((key: string, params?: any) => (params ? `${ key }:${ JSON.stringify(params) }` : key)),
5
5
  privateRegistryEnabled: false,
6
- normanCluster: { importedConfig: { privateRegistryURL: null } },
7
- isImportedCluster: true,
6
+ normanCluster: { importedConfig: {} },
7
+ $store: { getters: { 'management/byId': () => null } },
8
8
  ...overrides,
9
9
  });
10
10
 
11
- describe('privateRegistryRequired', () => {
12
- it('should return undefined when the cluster is not imported', () => {
13
- const ctx = makeCtx({ isImportedCluster: false, privateRegistryEnabled: true });
14
- const rule = privateRegistryRequired(ctx);
15
-
16
- expect(rule()).toBeUndefined();
17
- });
11
+ const storeWithGlobalRegistry = { getters: { 'management/byId': () => ({ value: 'registry.global.io' }) } };
18
12
 
19
- it('should default isImportedCluster to true when the property is absent from the context', () => {
20
- const ctx: any = {
21
- t: jest.fn((key: string, params?: any) => (params ? `${ key }:${ JSON.stringify(params) }` : key)),
13
+ describe('privateRegistryRequired', () => {
14
+ it('should return undefined when a global default registry is configured', () => {
15
+ const ctx = makeCtx({
22
16
  privateRegistryEnabled: true,
23
- normanCluster: { importedConfig: { privateRegistryURL: null } },
24
- };
17
+ $store: storeWithGlobalRegistry,
18
+ });
25
19
  const rule = privateRegistryRequired(ctx);
26
20
 
27
- expect(rule()).toStrictEqual('validation.required:{"key":"cluster.privateRegistry.label"}');
21
+ expect(rule()).toBeUndefined();
28
22
  });
29
23
 
30
24
  it('should return undefined when the registry toggle is off', () => {
@@ -73,4 +67,22 @@ describe('privateRegistryRequired', () => {
73
67
 
74
68
  expect(rule()).toBeUndefined();
75
69
  });
70
+
71
+ it('should handle $store getter throwing gracefully', () => {
72
+ const ctx = makeCtx({
73
+ privateRegistryEnabled: true,
74
+ normanCluster: { importedConfig: {} },
75
+ $store: {
76
+ getters: {
77
+ get 'management/byId'() {
78
+ throw new Error('no store');
79
+ }
80
+ }
81
+ },
82
+ });
83
+ const rule = privateRegistryRequired(ctx);
84
+
85
+ // Falls through to validation since no global default detected
86
+ expect(rule()).toStrictEqual('validation.required:{"key":"cluster.privateRegistry.label"}');
87
+ });
76
88
  });
@@ -1,19 +1,30 @@
1
+ import { VuexStore } from 'types/store/vuex';
1
2
  import { Translation } from '@shell/types/t';
2
3
  import formRulesGenerator from '@shell/utils/validators/formRules';
4
+ import { SETTING } from '@shell/config/settings';
5
+ import { MANAGEMENT } from '@shell/config/types';
3
6
 
4
7
  interface PrivateRegistryRuleContext {
5
8
  t: Translation;
6
9
  privateRegistryEnabled: boolean;
7
10
  normanCluster: { importedConfig?: { privateRegistryURL?: string | null } } | null;
8
- isImportedCluster?: boolean;
11
+ $store: VuexStore
9
12
  }
10
13
 
11
14
  export function privateRegistryRequired(ctx: PrivateRegistryRuleContext) {
12
15
  return () => {
13
- // Check existence using `in` rather than direct access to avoid Vue's render-time warning
14
- const isImported = 'isImportedCluster' in ctx ? !!ctx.isImportedCluster : true;
16
+ let hasGlobalDefault = false;
15
17
 
16
- if (!isImported || !ctx.privateRegistryEnabled) {
18
+ try {
19
+ // settings are loaded when the dashboard loads so using a synchronous getter is reliable here
20
+ const globalRegistrySetting = ctx.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.SYSTEM_DEFAULT_REGISTRY);
21
+
22
+ hasGlobalDefault = !!globalRegistrySetting?.value;
23
+ } catch {
24
+ // if no setting proceed like there's no global default
25
+ }
26
+
27
+ if (!ctx.privateRegistryEnabled || hasGlobalDefault) {
17
28
  return undefined;
18
29
  }
19
30
  const url = ctx.normanCluster?.importedConfig?.privateRegistryURL;