@rancher/shell 3.0.12-rc.4 → 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 (159) 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/styles/global/_button.scss +1 -1
  9. package/assets/translations/en-us.yaml +49 -14
  10. package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
  11. package/chart/rancher-monitoring-dashboards/index.vue +155 -0
  12. package/cloud-credential/__tests__/generic.test.ts +132 -0
  13. package/cloud-credential/generic.vue +33 -3
  14. package/components/ActionDropdownShell.vue +5 -3
  15. package/components/ButtonGroup.vue +26 -1
  16. package/components/CruResource.vue +51 -2
  17. package/components/EtcdInfoBanner.vue +2 -12
  18. package/components/GrafanaDashboard.vue +2 -8
  19. package/components/PromptRestore.vue +93 -32
  20. package/components/Questions/index.vue +1 -0
  21. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
  22. package/components/Resource/Detail/Masthead/index.vue +8 -3
  23. package/components/ResourceTable.vue +1 -0
  24. package/components/SortableTable/index.vue +4 -3
  25. package/components/Wizard.vue +14 -1
  26. package/components/__tests__/ButtonGroup.test.ts +56 -0
  27. package/components/__tests__/PromptRestore.test.ts +169 -19
  28. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  29. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  30. package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
  31. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  32. package/components/form/FileSelector.vue +39 -1
  33. package/components/form/PrivateRegistry.constants.ts +7 -0
  34. package/components/form/PrivateRegistry.vue +253 -18
  35. package/components/form/Security.vue +1 -1
  36. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  37. package/components/form/__tests__/FileSelector.test.ts +23 -0
  38. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  39. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  40. package/components/formatter/EtcdSnapshotName.vue +73 -0
  41. package/components/formatter/Translate.vue +22 -1
  42. package/components/nav/Group.vue +5 -10
  43. package/components/nav/Header.vue +8 -1
  44. package/components/nav/TopLevelMenu.vue +1 -30
  45. package/components/nav/__tests__/Group.test.ts +61 -0
  46. package/components/templates/default.vue +7 -0
  47. package/config/features.js +1 -0
  48. package/config/labels-annotations.js +3 -0
  49. package/config/product/manager.js +6 -0
  50. package/config/secret.ts +10 -0
  51. package/config/settings.ts +6 -2
  52. package/config/table-headers.js +5 -2
  53. package/config/types.js +7 -0
  54. package/core/__tests__/plugin-products-apply.test.ts +787 -0
  55. package/core/__tests__/plugin-products-extend.test.ts +189 -0
  56. package/core/__tests__/plugin-products-helpers.test.ts +81 -78
  57. package/core/__tests__/plugin-products-new.test.ts +389 -0
  58. package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
  59. package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
  60. package/core/plugin-products-base.ts +201 -93
  61. package/core/plugin-products-extending.ts +1 -1
  62. package/core/plugin-products-external.ts +380 -0
  63. package/core/plugin-products-helpers.ts +28 -24
  64. package/core/plugin-products-internal.ts +207 -0
  65. package/core/plugin-products-top-level.ts +10 -5
  66. package/core/plugin-products-type-guards.ts +17 -6
  67. package/core/plugin-products.ts +5 -7
  68. package/core/plugin-types.ts +1 -389
  69. package/core/plugin.ts +10 -11
  70. package/core/types.ts +6 -202
  71. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
  72. package/detail/pod.vue +1 -1
  73. package/detail/provisioning.cattle.io.cluster.vue +79 -5
  74. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  75. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  76. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  77. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
  78. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  79. package/edit/auth/AuthProviderWarningBanners.vue +12 -0
  80. package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
  81. package/edit/auth/__tests__/azuread.test.ts +1 -0
  82. package/edit/auth/__tests__/github.test.ts +1 -0
  83. package/edit/auth/__tests__/oidc.test.ts +1 -0
  84. package/edit/auth/__tests__/saml.test.ts +1 -0
  85. package/edit/auth/ldap/__tests__/index.test.ts +1 -0
  86. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  87. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  88. package/edit/fleet.cattle.io.helmop.vue +51 -5
  89. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  90. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  91. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  92. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  93. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  94. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  95. package/edit/provisioning.cattle.io.cluster/rke2.vue +6 -2
  96. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  97. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
  98. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  99. package/list/management.cattle.io.user.vue +24 -5
  100. package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
  101. package/machine-config/__tests__/generic.test.ts +163 -0
  102. package/machine-config/components/EC2Networking.vue +31 -7
  103. package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
  104. package/machine-config/generic.vue +32 -5
  105. package/mixins/resource-fetch.js +1 -1
  106. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
  107. package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
  108. package/models/__tests__/secret.test.ts +68 -1
  109. package/models/management.cattle.io.cluster.js +21 -3
  110. package/models/pod.js +25 -12
  111. package/models/provisioning.cattle.io.cluster.js +59 -9
  112. package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
  113. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  114. package/models/secret.js +19 -0
  115. package/models/workload.js +12 -7
  116. package/package.json +1 -1
  117. package/pages/auth/login.vue +1 -1
  118. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +569 -81
  119. package/pages/c/_cluster/apps/charts/install.vue +143 -28
  120. package/pages/c/_cluster/auth/config/index.vue +11 -4
  121. package/pages/c/_cluster/monitoring/index.vue +6 -1
  122. package/pkg/require-asset.lib.js +25 -0
  123. package/pkg/vue.config.js +7 -0
  124. package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
  125. package/plugins/dashboard-store/getters.js +0 -1
  126. package/plugins/dashboard-store/resource-class.js +62 -15
  127. package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
  128. package/plugins/steve/steve-class.js +43 -0
  129. package/plugins/steve/subscribe.js +11 -0
  130. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
  131. package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
  132. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  133. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  134. package/rancher-components/RcButton/index.ts +1 -1
  135. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  136. package/store/__tests__/auth.test.ts +18 -0
  137. package/store/__tests__/features.test.ts +131 -0
  138. package/store/__tests__/growl.test.ts +374 -0
  139. package/store/__tests__/modal.test.ts +131 -0
  140. package/store/__tests__/notifications.test.ts +466 -1
  141. package/store/__tests__/slideInPanel.test.ts +88 -0
  142. package/store/__tests__/type-map.utils.test.ts +433 -0
  143. package/store/__tests__/ui-context.test.ts +255 -0
  144. package/store/auth.js +11 -1
  145. package/store/features.js +5 -0
  146. package/store/plugins.js +6 -0
  147. package/types/rancher/steve.api.ts +2 -2
  148. package/types/shell/index.d.ts +120 -0
  149. package/types/store/dashboard-store.types.ts +2 -2
  150. package/types/store/type-map.ts +262 -1
  151. package/utils/__tests__/operation-cr.test.ts +34 -0
  152. package/utils/async.ts +2 -0
  153. package/utils/grafana.js +2 -17
  154. package/utils/monitoring.js +38 -1
  155. package/utils/operation-cr.js +19 -0
  156. package/utils/require-asset.ts +7 -0
  157. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  158. package/utils/validators/private-registry.ts +15 -4
  159. package/core/__tests__/plugin-products.test.ts +0 -4694
@@ -66,4 +66,126 @@ describe('component: SelectOrCreateAuthSecret', () => {
66
66
 
67
67
  expect(passwordLabeledInput!.props('labelKey')).toBe(expectedLabelKey);
68
68
  });
69
+
70
+ describe('GitHub App auth', () => {
71
+ const githubAppSetup = () => mount(SelectOrCreateAuthSecret, {
72
+ ...requiredSetup(),
73
+ props: {
74
+ mode: _EDIT,
75
+ namespace: 'default',
76
+ value: {},
77
+ allowGithubApp: true,
78
+ registerBeforeHook: () => {},
79
+ },
80
+ data() {
81
+ return { selected: AUTH_TYPE._GITHUB_APP } as any;
82
+ }
83
+ });
84
+
85
+ it('should render the three GitHub App fields when selected', () => {
86
+ const wrapper = githubAppSetup();
87
+
88
+ expect(wrapper.find('[data-testid="auth-secret-github-app-id"]').exists()).toBe(true);
89
+ expect(wrapper.find('[data-testid="auth-secret-github-app-installation-id"]').exists()).toBe(true);
90
+ expect(wrapper.find('[data-testid="auth-secret-github-app-private-key"]').exists()).toBe(true);
91
+ expect(wrapper.find('[data-testid="auth-secret-github-app-private-key-file"]').exists()).toBe(true);
92
+ });
93
+
94
+ it('should not narrow the select column for GitHub App (keeps span-6)', () => {
95
+ const wrapper = githubAppSetup();
96
+
97
+ expect((wrapper.vm as any).firstCol).toBe('col span-6');
98
+ });
99
+
100
+ it('should emit inputauthval with the GitHub App field values', async() => {
101
+ const wrapper = githubAppSetup();
102
+
103
+ await wrapper.setData({
104
+ githubAppId: 'app-id',
105
+ githubAppInstallationId: 'installation-id',
106
+ githubAppPrivateKey: 'private-key',
107
+ });
108
+
109
+ const emitted = wrapper.emitted('inputauthval') as any[];
110
+ const last = emitted[emitted.length - 1][0];
111
+
112
+ expect(last).toStrictEqual({
113
+ selected: AUTH_TYPE._GITHUB_APP,
114
+ publicKey: '',
115
+ privateKey: '',
116
+ githubAppId: 'app-id',
117
+ githubAppInstallationId: 'installation-id',
118
+ githubAppPrivateKey: 'private-key',
119
+ });
120
+ });
121
+
122
+ it('should populate the private key when a file is read', async() => {
123
+ const wrapper = githubAppSetup();
124
+
125
+ const fileSelector = wrapper.findComponent({ name: 'FileSelector' });
126
+
127
+ await fileSelector.vm.$emit('selected', 'key-from-file');
128
+
129
+ expect((wrapper.vm as any).githubAppPrivateKey).toBe('key-from-file');
130
+ });
131
+
132
+ it.each([
133
+ ['offer', true],
134
+ ['not offer', false],
135
+ ])('should %s the GitHub App create option when allowGithubApp is %p', (_, allowGithubApp) => {
136
+ const wrapper = mount(SelectOrCreateAuthSecret, {
137
+ ...requiredSetup(),
138
+ props: {
139
+ mode: _EDIT,
140
+ namespace: 'default',
141
+ value: {},
142
+ allowGithubApp,
143
+ registerBeforeHook: () => {},
144
+ },
145
+ });
146
+
147
+ const hasGithubAppOption = (wrapper.vm as any).options
148
+ .some((o: any) => o.value === AUTH_TYPE._GITHUB_APP);
149
+
150
+ expect(hasGithubAppOption).toBe(allowGithubApp);
151
+ });
152
+
153
+ it('should list existing GitHub App secrets but exclude plain Opaque secrets', () => {
154
+ const githubAppSecret = {
155
+ _type: 'Opaque',
156
+ isGithubApp: true,
157
+ id: 'default/gh-app',
158
+ dataPreview: '3 keys',
159
+ subTypeDisplay: 'Opaque',
160
+ metadata: { name: 'gh-app', namespace: 'default' },
161
+ };
162
+ const plainOpaqueSecret = {
163
+ _type: 'Opaque',
164
+ isGithubApp: false,
165
+ id: 'default/plain',
166
+ dataPreview: '1 key',
167
+ subTypeDisplay: 'Opaque',
168
+ metadata: { name: 'plain', namespace: 'default' },
169
+ };
170
+
171
+ const wrapper = mount(SelectOrCreateAuthSecret, {
172
+ ...requiredSetup(),
173
+ props: {
174
+ mode: _EDIT,
175
+ namespace: 'default',
176
+ value: {},
177
+ allowGithubApp: true,
178
+ registerBeforeHook: () => {},
179
+ },
180
+ data() {
181
+ return { allSecrets: [githubAppSecret, plainOpaqueSecret] } as any;
182
+ }
183
+ });
184
+
185
+ const optionValues = (wrapper.vm as any).options.map((o: any) => o.value);
186
+
187
+ expect(optionValues).toContain('default/gh-app');
188
+ expect(optionValues).not.toContain('default/plain');
189
+ });
190
+ });
69
191
  });
@@ -0,0 +1,73 @@
1
+ <script>
2
+ import { get } from '@shell/utils/object';
3
+
4
+ export default {
5
+ props: {
6
+ value: {
7
+ type: String,
8
+ default: ''
9
+ },
10
+ row: {
11
+ type: Object,
12
+ required: true
13
+ },
14
+ col: {
15
+ type: Object,
16
+ default: () => ({})
17
+ },
18
+ reference: {
19
+ type: String,
20
+ default: null,
21
+ },
22
+ getCustomDetailLink: {
23
+ type: Function,
24
+ default: null
25
+ }
26
+ },
27
+
28
+ computed: {
29
+ to() {
30
+ if (this.getCustomDetailLink) {
31
+ return this.getCustomDetailLink(this.row);
32
+ }
33
+ if ( this.row && this.reference ) {
34
+ return get(this.row, this.reference);
35
+ }
36
+
37
+ return this.row?.detailLocation;
38
+ },
39
+
40
+ showPredatesImportIcon() {
41
+ return !!this.row?.isSnapshotTooOld;
42
+ },
43
+
44
+ predatesImportMessage() {
45
+ return this.t('cluster.snapshot.predatesImportTooltip');
46
+ },
47
+ }
48
+ };
49
+ </script>
50
+
51
+ <template>
52
+ <span>
53
+ <router-link
54
+ v-if="to"
55
+ :to="to"
56
+ >
57
+ {{ value }}
58
+ </router-link>
59
+ <span v-else>
60
+ {{ value }}
61
+ <template v-if="!value && col.dashIfEmpty">
62
+ <span class="text-muted">&mdash;</span>
63
+ </template>
64
+ </span>
65
+ <i
66
+ v-if="showPredatesImportIcon"
67
+ v-clean-tooltip="{ content: predatesImportMessage, triggers: ['hover', 'touch', 'focus'] }"
68
+ v-stripped-aria-label="predatesImportMessage"
69
+ tabindex="0"
70
+ class="icon icon-error text-error ml-5"
71
+ />
72
+ </span>
73
+ </template>
@@ -1,4 +1,6 @@
1
1
  <script>
2
+ import { get } from '@shell/utils/object';
3
+
2
4
  export default {
3
5
  props: {
4
6
  row: {
@@ -6,6 +8,11 @@ export default {
6
8
  required: true,
7
9
  },
8
10
 
11
+ fallbackPath: {
12
+ type: String,
13
+ default: '',
14
+ },
15
+
9
16
  prefix: {
10
17
  type: String,
11
18
  default: '',
@@ -18,8 +25,22 @@ export default {
18
25
  },
19
26
 
20
27
  computed: {
28
+ l10nId() {
29
+ return `${ this.prefix ? `${ this.prefix }.` : '' }${ this.value || this.row.id }`;
30
+ },
31
+
32
+ l10nFallback() {
33
+ const fallback = this.value;
34
+
35
+ if (this.fallbackPath) {
36
+ return get(this.row, this.fallbackPath) || fallback;
37
+ }
38
+
39
+ return fallback;
40
+ },
41
+
21
42
  text() {
22
- return this.$store.getters['i18n/withFallback'](`${ this.prefix }.${ this.value || this.row.id }`, null, this.value);
43
+ return this.$store.getters['i18n/withFallback'](this.l10nId, null, this.l10nFallback);
23
44
  },
24
45
  },
25
46
  };
@@ -384,14 +384,12 @@ export default {
384
384
  user-select: none;
385
385
  text-transform: none;
386
386
  font-size: 14px;
387
+ height: 100%;
388
+ padding: 8px 0 8px 16px;
389
+ display: inline-flex;
390
+ align-items: center;
387
391
  }
388
392
 
389
- > H6 {
390
- text-transform: none;
391
- height: 100%;
392
- padding: 8px 0 8px 16px;
393
- }
394
-
395
393
  > A {
396
394
  display: block;
397
395
  box-sizing:border-box;
@@ -403,10 +401,6 @@ export default {
403
401
  &:focus{
404
402
  outline:none;
405
403
  }
406
- > H6 {
407
- text-transform: none;
408
- padding: 8px 0 8px 16px;
409
- }
410
404
  }
411
405
  }
412
406
 
@@ -574,6 +568,7 @@ export default {
574
568
  padding-left: 24px;
575
569
  display: flex;
576
570
  justify-content: space-between;
571
+ align-items: center;
577
572
  }
578
573
 
579
574
  A:focus {
@@ -30,6 +30,7 @@ import {
30
30
  RcDropdownTrigger
31
31
  } from '@components/RcDropdown';
32
32
  import { SLO_AUTH_PROVIDERS } from '@shell/store/auth';
33
+ import { CLUSTER_SHELL } from '@shell/store/features';
33
34
 
34
35
  export default {
35
36
 
@@ -147,10 +148,16 @@ export default {
147
148
  return true;
148
149
  },
149
150
 
151
+ // Does the user have permissions to use the shell
150
152
  shellEnabled() {
151
153
  return !!this.currentCluster?.links?.shell;
152
154
  },
153
155
 
156
+ // Is the feature flag enabled for cluster shell access?
157
+ shellFeatureEnabled() {
158
+ return !!this.$store.getters['features/get'](CLUSTER_SHELL);
159
+ },
160
+
154
161
  showKubeShell() {
155
162
  return !this.rootProduct?.hideKubeShell;
156
163
  },
@@ -629,7 +636,7 @@ export default {
629
636
  </button>
630
637
 
631
638
  <button
632
- v-if="showKubeShell"
639
+ v-if="showKubeShell && shellFeatureEnabled"
633
640
  id="btn-kubectl"
634
641
  v-clean-tooltip="t('nav.shellShortcut', {key: shellShortcut})"
635
642
  v-shortkey="{windows: ['ctrl', '`'], mac: ['meta', '`']}"
@@ -955,19 +955,6 @@ export default {
955
955
  <div
956
956
  class="footer"
957
957
  >
958
- <div
959
- v-if="canEditSettings"
960
- class="support"
961
- @click="hide()"
962
- >
963
- <router-link
964
- :to="{name: 'support'}"
965
- role="link"
966
- :aria-label="t('nav.ariaLabel.support')"
967
- >
968
- {{ t('nav.support', {hasSupport}) }}
969
- </router-link>
970
- </div>
971
958
  <div
972
959
  class="version"
973
960
  :class="{'version-small': largeAboutText}"
@@ -1574,10 +1561,6 @@ export default {
1574
1561
  margin: 20px 10px;
1575
1562
  width: 50px;
1576
1563
 
1577
- .support {
1578
- display: none;
1579
- }
1580
-
1581
1564
  .version{
1582
1565
  text-align: center;
1583
1566
 
@@ -1597,19 +1580,7 @@ export default {
1597
1580
  > * {
1598
1581
  flex: 1;
1599
1582
  color: var(--link);
1600
-
1601
- &:first-child {
1602
- text-align: left;
1603
- }
1604
- &:last-child {
1605
- text-align: right;
1606
- }
1607
- text-align: center;
1608
- }
1609
-
1610
- .support a:focus-visible {
1611
- @include focus-outline;
1612
- outline-offset: 4px;
1583
+ text-align: left;
1613
1584
  }
1614
1585
 
1615
1586
  .version {
@@ -64,4 +64,65 @@ describe('component: Group', () => {
64
64
 
65
65
  expect((wrapper.vm as any).hasActiveRoute()).toBe(true);
66
66
  });
67
+
68
+ describe('group header label', () => {
69
+ const mountGroup = (group: any) => shallowMount(Group as any, {
70
+ props: {
71
+ group, canCollapse: true, idPrefix: ''
72
+ },
73
+ global: {
74
+ stubs: { 'router-link': { template: '<a><slot /></a>' } },
75
+ mocks: {
76
+ $route: {
77
+ params: {}, path: '/test/route', fullPath: '/test/route', matched: []
78
+ },
79
+ $router: {
80
+ resolve: jest.fn().mockReturnValue({ path: '/test/route', fullPath: '/test/route' }),
81
+ getRoutes: jest.fn().mockReturnValue([])
82
+ },
83
+ t: (key: string) => key
84
+ }
85
+ }
86
+ });
87
+
88
+ it('renders the label in an h6 for a non-linked group header', () => {
89
+ const group = {
90
+ name: 'test',
91
+ label: 'My Group',
92
+ children: [
93
+ { route: { name: 'child-route', params: {} } }
94
+ ]
95
+ };
96
+
97
+ const wrapper = mountGroup(group);
98
+ const header = wrapper.find('.header');
99
+ const h6 = header.find('h6');
100
+
101
+ // Non-linked header: the label sits in a bare h6, not wrapped in a router-link
102
+ expect(header.find('a').exists()).toBe(false);
103
+ expect(h6.exists()).toBe(true);
104
+ expect(h6.find('span').html()).toContain('My Group');
105
+ });
106
+
107
+ it('renders the label in an h6 wrapped in a router-link for a group header with overview', () => {
108
+ const group = {
109
+ name: 'test',
110
+ label: 'My Group',
111
+ children: [
112
+ {
113
+ route: { name: 'overview-route' }, overview: true, exact: true
114
+ }
115
+ ]
116
+ };
117
+
118
+ const wrapper = mountGroup(group);
119
+ const link = wrapper.find('.header a');
120
+ const h6 = link.find('h6');
121
+
122
+ // Linked header: the same h6 label markup is wrapped in a router-link
123
+ expect(link.exists()).toBe(true);
124
+ expect(h6.exists()).toBe(true);
125
+ expect(h6.find('span').html()).toContain('My Group');
126
+ });
127
+ });
67
128
  });
@@ -25,6 +25,7 @@ import { getClusterFromRoute, getProductFromRoute } from '@shell/utils/router';
25
25
  import SideNav from '@shell/components/SideNav';
26
26
  import { Layout } from '@shell/types/window-manager';
27
27
  import { RcButton } from '@components/RcButton';
28
+ import { CLUSTER_SHELL } from '@shell/store/features';
28
29
 
29
30
  const SET_LOGIN_ACTION = 'set-as-login';
30
31
 
@@ -140,6 +141,7 @@ export default {
140
141
  debugger;
141
142
  },
142
143
 
144
+ // Open the shell for the current cluster if the user has permissions and the feature is enabled (invoked via keyboard shortcut)
143
145
  async toggleShell() {
144
146
  const clusterId = this.$route.params.cluster;
145
147
 
@@ -147,6 +149,11 @@ export default {
147
149
  return;
148
150
  }
149
151
 
152
+ // Cluster shell is disabled via feature flag
153
+ if (!this.$store.getters['features/get'](CLUSTER_SHELL)) {
154
+ return;
155
+ }
156
+
150
157
  const cluster = await this.$store.dispatch('management/find', {
151
158
  type: MANAGEMENT.CLUSTER,
152
159
  id: clusterId,
@@ -4,3 +4,4 @@ export const ONE_WAY = [
4
4
 
5
5
  export const HARVESTER_NAME = 'harvester';
6
6
  export const SCHEDULING_CUSTOMIZATION = 'cluster-agent-scheduling-customization';
7
+ export const IMPORTED_DAY_2_OPS = 'imported-day-2-ops';
@@ -20,6 +20,7 @@ export const IMPORTED_CLUSTER_VERSION_MANAGEMENT = 'rancher.io/imported-cluster-
20
20
  export const UI_PROJECT_SECRET = 'management.cattle.io/project-scoped-secret';
21
21
  export const UI_PROJECT_SECRET_COPY = 'management.cattle.io/project-scoped-secret-copy';
22
22
  export const SERVICE_LINKS = 'ui.rancher/service-links';
23
+ export const NODE_DRIVER_FIELD_HINTS = 'io.cattle.nodedriver/ui-field-hints';
23
24
 
24
25
  export const KUBERNETES = {
25
26
  SERVICE_ACCOUNT_UID: 'kubernetes.io/service-account.uid',
@@ -150,6 +151,8 @@ export const RKE = { EXTERNAL_IP: 'rke.cattle.io/external-ip' };
150
151
 
151
152
  export const SNAPSHOT = { CLUSTER_NAME: 'rke.cattle.io/cluster-name' };
152
153
 
154
+ export const OPERATION_ANNOTATIONS = { ENABLED: 'operations.cattle.io/ops-enabled' };
155
+
153
156
  export const ISTIO = { AUTO_INJECTION: 'istio-injection' };
154
157
 
155
158
  const CATTLE_REGEX = /cattle\.io\//;
@@ -10,6 +10,7 @@ import {
10
10
  HCI,
11
11
  MANAGEMENT,
12
12
  SNAPSHOT,
13
+ OPERATION,
13
14
  VIRTUAL_TYPES,
14
15
  HOSTED_PROVIDER,
15
16
  SAVED_COUNTS
@@ -80,6 +81,11 @@ export function init(store) {
80
81
  configureType(SNAPSHOT, { depaginate: true });
81
82
  configureType(CATALOG.CLUSTER_REPO, { listCreateButtonLabelKey: 'catalog.repo.add' });
82
83
 
84
+ // Day 2 operation CRDs - read-only, not user-creatable or editable
85
+ configureType(OPERATION.ETCD_SNAPSHOT, { isCreatable: false, isEditable: false });
86
+ configureType(OPERATION.ETCD_SNAPSHOT_RESTORE, { isCreatable: false, isEditable: false });
87
+ configureType(OPERATION.ENCRYPTION_KEY_ROTATE, { isCreatable: false, isEditable: false });
88
+
83
89
  configureType(CAPI.RANCHER_CLUSTER, {
84
90
  showListMasthead: false, namespaced: false, alias: [HCI.CLUSTER]
85
91
  });
package/config/secret.ts CHANGED
@@ -13,3 +13,13 @@ export const SECRET_TYPES = {
13
13
  RKE_AUTH_CONFIG: 'rke.cattle.io/auth-config',
14
14
  FLEET_OCI_STORAGE: 'fleet.cattle.io/bundle-oci-storage/v1alpha1'
15
15
  };
16
+
17
+ /**
18
+ * Data keys for a Fleet GitHub App authentication secret (stored as an Opaque secret).
19
+ * Shared so the create flows stay in sync.
20
+ */
21
+ export const GITHUB_APP_SECRET_KEYS = {
22
+ APP_ID: 'github_app_id',
23
+ INSTALLATION_ID: 'github_app_installation_id',
24
+ PRIVATE_KEY: 'github_app_private_key',
25
+ };
@@ -67,6 +67,7 @@ export const SETTING = {
67
67
  UI_DASHBOARD_HARVESTER_LEGACY_PLUGIN: 'ui-dashboard-harvester-legacy-plugin',
68
68
  UI_OFFLINE_PREFERRED: 'ui-offline-preferred',
69
69
  SYSTEM_DEFAULT_REGISTRY: 'system-default-registry',
70
+ SYSTEM_DEFAULT_REGISTRY_PULL_SECRETS: 'system-default-registry-pull-secrets',
70
71
  UI_ISSUES: 'ui-issues',
71
72
  PL: 'ui-pl',
72
73
  PL_RANCHER_VALUE: 'rancher',
@@ -124,6 +125,7 @@ export const SETTING = {
124
125
  */
125
126
  DYNAMIC_CONTENT_ENABLED: 'ui-content-enabled',
126
127
  DYNAMIC_CONTENT_ENDPOINT: 'ui-content-endpoint',
128
+ IMPORTED_CLUSTER_DAY2_OPS_DEFAULT: 'imported-cluster-day2-ops-enabled'
127
129
  } as const;
128
130
 
129
131
  // These are the settings that are allowed to be edited via the UI
@@ -163,6 +165,7 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
163
165
  [SETTING.SERVER_URL]: { kind: 'url', canReset: true },
164
166
  [SETTING.RKE_METADATA_CONFIG]: { kind: 'json' },
165
167
  [SETTING.SYSTEM_DEFAULT_REGISTRY]: {},
168
+ [SETTING.SYSTEM_DEFAULT_REGISTRY_PULL_SECRETS]: {},
166
169
  [SETTING.UI_DASHBOARD_INDEX]: {},
167
170
  [SETTING.UI_OFFLINE_PREFERRED]: {
168
171
  kind: 'enum',
@@ -184,12 +187,12 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
184
187
  ruleSet: [{ name: 'minValue', factoryArg: 1 }]
185
188
  },
186
189
  [SETTING.IMPORTED_CLUSTER_VERSION_MANAGEMENT]: { kind: 'boolean' },
190
+ [SETTING.IMPORTED_CLUSTER_DAY2_OPS_DEFAULT]: { kind: 'boolean' },
187
191
  // Configuration setup for agent configuration. Setting this up will activate the specific banner configuration.
188
192
  [SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS]: { kind: 'json', agent: AGENT_CONFIGURATION_TYPES.CLUSTER },
189
193
  [SETTING.CLUSTER_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET]: { kind: 'json', agent: AGENT_CONFIGURATION_TYPES.CLUSTER },
190
194
  [SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS]: { kind: 'json', agent: AGENT_CONFIGURATION_TYPES.FLEET },
191
- [SETTING.FLEET_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET]: { kind: 'json', agent: AGENT_CONFIGURATION_TYPES.FLEET }
192
-
195
+ [SETTING.FLEET_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET]: { kind: 'json', agent: AGENT_CONFIGURATION_TYPES.FLEET },
193
196
  };
194
197
 
195
198
  /**
@@ -206,6 +209,7 @@ export const PROVISIONING_SETTINGS = [
206
209
  SETTING.CLUSTER_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET,
207
210
  SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS,
208
211
  SETTING.FLEET_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET,
212
+ SETTING.IMPORTED_CLUSTER_DAY2_OPS_DEFAULT
209
213
  ];
210
214
 
211
215
  /**
@@ -1072,11 +1072,14 @@ export const ROLE = {
1072
1072
  export const FEATURE_DESCRIPTION = {
1073
1073
  name: 'description',
1074
1074
  labelKey: 'tableHeaders.description',
1075
- value: 'status.description',
1075
+ value: 'id',
1076
1076
  align: 'left',
1077
1077
  sort: ['status.description'],
1078
1078
  formatter: 'Translate',
1079
- formatterOpts: { prefix: 'featureFlags.description' },
1079
+ formatterOpts: {
1080
+ prefix: 'featureFlags.description',
1081
+ fallbackPath: 'status.description'
1082
+ },
1080
1083
  };
1081
1084
 
1082
1085
  export const STATE_NORMAN = {
package/config/types.js CHANGED
@@ -219,6 +219,12 @@ export const LONGHORN_VERSION_V2 = 'LonghornV2';
219
219
 
220
220
  export const SNAPSHOT = 'rke.cattle.io.etcdsnapshot';
221
221
 
222
+ export const OPERATION = {
223
+ ETCD_SNAPSHOT: 'operation.cattle.io.etcdsnapshotsave',
224
+ ETCD_SNAPSHOT_RESTORE: 'operation.cattle.io.etcdsnapshotrestore',
225
+ ENCRYPTION_KEY_ROTATE: 'operation.cattle.io.encryptionkeyrotation',
226
+ };
227
+
222
228
  // --------------------------------------
223
229
  // 2. Only if Rancher is installed
224
230
  // --------------------------------------
@@ -400,6 +406,7 @@ export const AUTH_TYPE = {
400
406
  _S3: '_S3',
401
407
  _RKE: '_RKE',
402
408
  _IMAGE_PULL_SECRET: '_IPS',
409
+ _GITHUB_APP: '_GITHUB_APP',
403
410
  };
404
411
 
405
412
  export const LOCAL_CLUSTER = 'local';