@rancher/shell 3.0.9 → 3.0.11

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 (104) hide show
  1. package/assets/styles/base/_color.scss +4 -0
  2. package/assets/styles/themes/_light.scss +6 -6
  3. package/assets/styles/themes/_modern.scss +14 -6
  4. package/assets/translations/en-us.yaml +9 -10
  5. package/chart/__tests__/rancher-backup-index.test.ts +248 -0
  6. package/chart/rancher-backup/index.vue +41 -2
  7. package/components/BrandImage.vue +6 -5
  8. package/components/ConsumptionGauge.vue +12 -4
  9. package/components/CopyToClipboard.vue +28 -0
  10. package/components/CopyToClipboardText.vue +4 -0
  11. package/components/CruResource.vue +1 -0
  12. package/components/DynamicContent/DynamicContentIcon.vue +3 -2
  13. package/components/ExplorerProjectsNamespaces.vue +1 -4
  14. package/components/GlobalRoleBindings.vue +1 -5
  15. package/components/LazyImage.vue +2 -1
  16. package/components/Resource/Detail/Card/Scaler.vue +4 -4
  17. package/components/ResourceDetail/index.vue +0 -21
  18. package/components/Tabbed/index.vue +6 -0
  19. package/components/__tests__/ConsumptionGauge.test.ts +31 -0
  20. package/components/__tests__/CruResource.test.ts +35 -1
  21. package/components/form/ProjectMemberEditor.vue +0 -10
  22. package/components/nav/TopLevelMenu.helper.ts +7 -79
  23. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +2 -53
  24. package/composables/useIsNewDetailPageEnabled.test.ts +98 -0
  25. package/composables/useIsNewDetailPageEnabled.ts +12 -0
  26. package/config/private-label.js +2 -1
  27. package/config/product/apps.js +1 -0
  28. package/config/product/explorer.js +11 -1
  29. package/config/table-headers.js +0 -9
  30. package/config/types.js +0 -1
  31. package/core/__tests__/extension-manager-impl.test.js +187 -2
  32. package/core/extension-manager-impl.js +4 -2
  33. package/core/plugin-helpers.ts +31 -0
  34. package/detail/__tests__/node.test.ts +83 -0
  35. package/detail/management.cattle.io.oidcclient.vue +2 -1
  36. package/detail/node.vue +1 -0
  37. package/edit/auth/github-app-steps.vue +2 -0
  38. package/edit/auth/github-steps.vue +2 -0
  39. package/edit/catalog.cattle.io.clusterrepo.vue +17 -3
  40. package/edit/cloudcredential.vue +2 -1
  41. package/edit/management.cattle.io.user.vue +60 -35
  42. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -6
  43. package/edit/provisioning.cattle.io.cluster/index.vue +5 -4
  44. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -2
  45. package/edit/secret/generic.vue +1 -0
  46. package/edit/secret/index.vue +2 -1
  47. package/edit/service.vue +2 -14
  48. package/edit/token.vue +29 -68
  49. package/list/management.cattle.io.feature.vue +7 -1
  50. package/list/provisioning.cattle.io.cluster.vue +0 -49
  51. package/mixins/brand.js +2 -1
  52. package/models/catalog.cattle.io.clusterrepo.js +9 -0
  53. package/models/cluster.x-k8s.io.machinedeployment.js +8 -3
  54. package/models/management.cattle.io.authconfig.js +2 -1
  55. package/models/management.cattle.io.cluster.js +4 -3
  56. package/models/monitoring.coreos.com.receiver.js +11 -6
  57. package/models/provisioning.cattle.io.cluster.js +2 -2
  58. package/models/token.js +0 -4
  59. package/package.json +12 -12
  60. package/pages/account/index.vue +67 -96
  61. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +66 -9
  62. package/pages/c/_cluster/apps/charts/index.vue +3 -8
  63. package/pages/c/_cluster/apps/charts/install.vue +8 -9
  64. package/pages/c/_cluster/explorer/index.vue +2 -19
  65. package/pages/c/_cluster/istio/index.vue +4 -2
  66. package/pages/c/_cluster/longhorn/index.vue +2 -1
  67. package/pages/c/_cluster/monitoring/index.vue +2 -2
  68. package/pages/c/_cluster/neuvector/index.vue +2 -1
  69. package/pages/c/_cluster/settings/performance.vue +0 -5
  70. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +2 -1
  71. package/pages/c/_cluster/uiplugins/index.vue +2 -1
  72. package/pkg/auto-import.js +41 -0
  73. package/plugins/dashboard-store/resource-class.js +2 -2
  74. package/plugins/steve/__tests__/steve-class.test.ts +1 -1
  75. package/plugins/steve/steve-class.js +3 -3
  76. package/plugins/steve/steve-pagination-utils.ts +2 -5
  77. package/plugins/steve/subscribe.js +29 -4
  78. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +7 -7
  79. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +5 -2
  80. package/rancher-components/RcButton/RcButton.vue +3 -3
  81. package/rancher-components/RcButtonSplit/RcButtonSplit.test.ts +253 -0
  82. package/rancher-components/RcButtonSplit/RcButtonSplit.vue +158 -0
  83. package/rancher-components/RcButtonSplit/index.ts +1 -0
  84. package/rancher-components/RcIcon/types.ts +2 -2
  85. package/rancher-components/RcSection/RcSection.test.ts +323 -0
  86. package/rancher-components/RcSection/RcSection.vue +252 -0
  87. package/rancher-components/RcSection/RcSectionActions.test.ts +212 -0
  88. package/rancher-components/RcSection/RcSectionActions.vue +85 -0
  89. package/rancher-components/RcSection/RcSectionBadges.test.ts +149 -0
  90. package/rancher-components/RcSection/RcSectionBadges.vue +29 -0
  91. package/rancher-components/RcSection/index.ts +12 -0
  92. package/rancher-components/RcSection/types.ts +86 -0
  93. package/scripts/test-plugins-build.sh +9 -8
  94. package/types/shell/index.d.ts +93 -108
  95. package/utils/__tests__/require-asset.test.ts +98 -0
  96. package/utils/async.ts +1 -5
  97. package/utils/brand.ts +3 -1
  98. package/utils/favicon.js +4 -3
  99. package/utils/require-asset.ts +95 -0
  100. package/utils/style.ts +17 -0
  101. package/utils/units.js +14 -5
  102. package/vue.config.js +4 -3
  103. package/components/HarvesterServiceAddOnConfig.vue +0 -207
  104. package/models/ext.cattle.io.token.js +0 -48
@@ -29,3 +29,7 @@ $gray003: #FFF;
29
29
  $gray004: #6C6C76;
30
30
  $gray005: #4A4B52;
31
31
  $gray006: #1b1c21;
32
+ $gray007: #161C24;
33
+ $gray008: #262831;
34
+ $gray009: #6C6C77;
35
+ $gray010: #B6B6C3;
@@ -571,17 +571,17 @@
571
571
  --rc-success: #{$green001};
572
572
  --rc-success-secondary: #{$green002};
573
573
 
574
- --rc-warning: #{$yellow001};
575
- --rc-warning-secondary: #{$yellow002};
574
+ --rc-warning: #{$yellow002};
575
+ --rc-warning-secondary: #{$yellow001};
576
576
 
577
577
  --rc-error: #{$red001};
578
578
  --rc-error-secondary: #{$red002};
579
579
 
580
- --rc-unknown: #{$gray001};
581
- --rc-unknown-secondary: #{$gray004};
580
+ --rc-unknown: #{$gray004};
581
+ --rc-unknown-secondary: #{$gray001};
582
582
 
583
- --rc-none: #{$gray002};
584
- --rc-none-secondary: #{$gray004};
583
+ --rc-none: #{$gray004};
584
+ --rc-none-secondary: #{$gray002};
585
585
 
586
586
  --rc-primary-hover: #{$blue003};
587
587
 
@@ -701,17 +701,17 @@ BODY, .theme-light {
701
701
  --rc-success: #{$green001};
702
702
  --rc-success-secondary: #{$green002};
703
703
 
704
- --rc-warning: #{$yellow001};
705
- --rc-warning-secondary: #{$yellow002};
704
+ --rc-warning: #{$yellow002};
705
+ --rc-warning-secondary: #{$yellow001};
706
706
 
707
707
  --rc-error: #{$red001};
708
708
  --rc-error-secondary: #{$red002};
709
709
 
710
- --rc-unknown: #{$gray001};
711
- --rc-unknown-secondary: #{$gray004};
710
+ --rc-unknown: #{$gray004};
711
+ --rc-unknown-secondary: #{$gray001};
712
712
 
713
- --rc-none: #{$gray002};
714
- --rc-none-secondary: #{$gray004};
713
+ --rc-none: #{$gray004};
714
+ --rc-none-secondary: #{$gray002};
715
715
 
716
716
  --rc-primary-hover: #{$blue003};
717
717
 
@@ -724,6 +724,10 @@ BODY, .theme-light {
724
724
  --rc-disabled-background: #{$gray001};
725
725
  --rc-disabled-text-color: #{$gray004};
726
726
 
727
+ --rc-section-background-primary: #{$lightest};
728
+ --rc-section-background-secondary: #{$lighter};
729
+ --rc-section-action-color: #{$gray009};
730
+
727
731
  --rc-image-bg: #{$lightest};
728
732
  --rc-image-color: #{$darkest};
729
733
 
@@ -1068,6 +1072,10 @@ BODY, .theme-dark {
1068
1072
  --rc-disabled-background: #{$gray005};
1069
1073
  --rc-disabled-text-color: #{$gray004};
1070
1074
 
1075
+ --rc-section-background-primary: #{$gray007};
1076
+ --rc-section-background-secondary: #{$gray008};
1077
+ --rc-section-action-color: #{$gray010};
1078
+
1071
1079
  --rc-image-bg: #{$lightest};
1072
1080
  --rc-image-color: #{$darkest};
1073
1081
 
@@ -30,6 +30,7 @@ generic:
30
30
  comma: ', '
31
31
  copy: Copy
32
32
  copyToClipboard: Copy text to Clipboard
33
+ copyValueToClipboard: 'Copy {value} to Clipboard'
33
34
  copiedToClipboard: Text copied to Clipboard
34
35
  create: Create
35
36
  created: Created
@@ -438,7 +439,6 @@ accountAndKeys:
438
439
  notAllowed: You do not have permission to manage API Keys
439
440
  apiEndpoint: "API Endpoint:"
440
441
  copyApiEnpoint: Copy API Endpoint to clipboard
441
- normanTokenDeprecation: The API Keys feature is being migrated to a new API. Any existing API Keys from the legacy API will continue to work, but new API Keys will be created using the new API.
442
442
  add:
443
443
  description:
444
444
  label: Description
@@ -464,9 +464,7 @@ accountAndKeys:
464
464
  month: Months
465
465
  year: Years
466
466
  scope: Scope
467
- userPrincipal: User Principal
468
467
  noScope: No Scope
469
- enabled: Token enabled
470
468
  info:
471
469
  accessKey: Access Key
472
470
  secretKey: Secret Key
@@ -475,7 +473,7 @@ accountAndKeys:
475
473
  keyCreated: A new API Key has been created
476
474
  bearerTokenTip: "Access Key and Secret Key can be sent as the username and password for HTTP Basic auth to authorize requests. You can also combine them to use as a Bearer token:"
477
475
  ttlLimitedWarning: The Expiry time for this API Key was reduced due to system configuration
478
- expiryOptionsWithNever: Since "auth-token-max-ttl-minutes" is set to <= 0, the API Key will not expire unless the "Automatically expire" option is set to "Custom" and a custom expiry time is set.
476
+
479
477
  addClusterMemberDialog:
480
478
  title: Add Cluster Member
481
479
 
@@ -1020,6 +1018,11 @@ asyncButton:
1020
1018
  waiting: Generating&hellip;
1021
1019
 
1022
1020
  backupRestoreOperator:
1021
+ monitoring:
1022
+ label: Monitoring
1023
+ enableMetrics: Enable Metrics
1024
+ enableServiceMonitor: Enable ServiceMonitor
1025
+ serviceMonitorTooltip: Requires rancher-monitoring to be installed before this can be enabled.
1023
1026
  backup:
1024
1027
  label: Resource Set
1025
1028
  description: The Resource Set determines which resources the backup-restore-operator collects in a backup
@@ -1376,6 +1379,7 @@ catalog:
1376
1379
  releaseName: Release Name
1377
1380
  releaseNamespace: Release Namespace
1378
1381
  repo:
1382
+ add: Add Repository
1379
1383
  action:
1380
1384
  refresh: Refresh
1381
1385
  all: All
@@ -2721,8 +2725,6 @@ cluster:
2721
2725
  v2: RKE2/K3s
2722
2726
  validation:
2723
2727
  iamInstanceProfileName: If the Amazon cloud provider is selected the "IAM Instance Profile Name" must be defined for each Machine Pool
2724
- capi:
2725
- notSupported: Managing clusters provisioned using CAPI infrastructure providers via the UI is currently limited to Rancher Turtles provisioned clusters.
2726
2728
 
2727
2729
  clusterIndexPage:
2728
2730
  hardwareResourceGauge:
@@ -5031,6 +5033,7 @@ node:
5031
5033
  cpu: CPU
5032
5034
  memory: MEMORY
5033
5035
  pods: PODS
5036
+ running: Running
5034
5037
  diskPressure: Disk Pressure
5035
5038
  kubelet: kubelet
5036
5039
  memoryPressure: Memory Pressure
@@ -5775,7 +5778,6 @@ projectMembers:
5775
5778
  createNs: Create Namespaces
5776
5779
  configmapsManage: Manage Config Maps
5777
5780
  ingressManage: Manage Ingress
5778
- projectcatalogsManage: Manage Project Catalogs
5779
5781
  projectroletemplatebindingsManage: Manage Project Members
5780
5782
  secretsManage: Manage Secrets
5781
5783
  serviceaccountsManage: Manage Service Accounts
@@ -5785,7 +5787,6 @@ projectMembers:
5785
5787
  configmapsView: View Config Maps
5786
5788
  ingressView: View Ingress
5787
5789
  monitoringUiView: View Monitoring
5788
- projectcatalogsView: View Project Catalogs
5789
5790
  projectroletemplatebindingsView: View Project Members
5790
5791
  secretsView: View Secrets
5791
5792
  serviceaccountsView: View Service Accounts
@@ -6857,7 +6858,6 @@ storageClass:
6857
6858
  tooltip: By default the default storage class on the host Harvester cluster is used.
6858
6859
 
6859
6860
  tableHeaders:
6860
- isLegacy: Legacy
6861
6861
  assuredConcurrencyShares: Assured Concurrency Shares
6862
6862
  autoscaler: Autoscaler
6863
6863
  accessKey: Access Key
@@ -8949,7 +8949,6 @@ performance:
8949
8949
  label: Server-side Pagination
8950
8950
  description: By default Lists will fetch all resources and paginate (create the page given sort and filter settings) locally in the browser. Server-Side Pagination moves this out from the browser to the server. This improves performance of the UI, especially in systems with lots of resources.
8951
8951
  applicable: "Server-side pagination applies to Resource Types"
8952
- featureFlag: Enabling/Disabling <i class="mr-5">"Server-side Pagination"</i> is now solely done so via the <i class="mr-5">ui-sql-cache</i>&nbsp;<a href="{ffUrl}">Feature Flag</a>
8953
8952
  resources:
8954
8953
  generic: most resources in the cluster's 'More Resources' section
8955
8954
  all: All Resources
@@ -0,0 +1,248 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import RancherBackup from '@shell/chart/rancher-backup/index.vue';
3
+ import { set } from '@shell/utils/object';
4
+
5
+ describe('rancher-backup: index', () => {
6
+ const defaultMocks = {
7
+ $store: {
8
+ dispatch: jest.fn().mockResolvedValue([]),
9
+ getters: {
10
+ 'i18n/t': (text: string) => text,
11
+ t: (text: string) => text,
12
+ 'cluster/all': () => [],
13
+ 'cluster/paginationEnabled': () => false,
14
+ currentStore: () => 'cluster',
15
+ currentCluster: () => ({ id: 'local' }),
16
+ getStoreNameByProductId: 'cluster',
17
+ }
18
+ },
19
+ $fetchState: { pending: false }
20
+ };
21
+
22
+ const defaultStubs = {
23
+ Tab: { template: '<div><slot /></div>' },
24
+ Tabbed: { template: '<div><slot /></div>' },
25
+ S3: true,
26
+ RadioGroup: true,
27
+ LabeledInput: true,
28
+ LabeledSelect: true,
29
+ Banner: true,
30
+ Checkbox: true
31
+ };
32
+
33
+ const createWrapper = (propsData = {}, mocks = {}, monitoringInstalled = false) => {
34
+ return shallowMount(RancherBackup, {
35
+ props: {
36
+ value: {},
37
+ mode: 'create',
38
+ ...propsData
39
+ },
40
+ global: {
41
+ mocks: {
42
+ ...defaultMocks,
43
+ ...mocks
44
+ },
45
+ stubs: defaultStubs
46
+ },
47
+ computed: {
48
+ monitoringStatus: () => ({ installed: monitoringInstalled }),
49
+ radioOptions: () => ({
50
+ options: ['none', 's3', 'pickSC', 'pickPV'],
51
+ labels: ['None', 'S3', 'Storage Class', 'Persistent Volume']
52
+ })
53
+ }
54
+ });
55
+ };
56
+
57
+ describe('monitoring section initialization', () => {
58
+ it('should initialize monitoring object with default values when not present', async() => {
59
+ const value: Record<string, any> = {};
60
+
61
+ // Call set directly to test the initialization logic
62
+ set(value, 'monitoring', {
63
+ metrics: { enabled: false },
64
+ serviceMonitor: { enabled: false }
65
+ });
66
+
67
+ expect(value).toHaveProperty('monitoring');
68
+ expect(value).toHaveProperty('monitoring.metrics.enabled', false);
69
+ expect(value).toHaveProperty('monitoring.serviceMonitor.enabled', false);
70
+ });
71
+
72
+ it('should not overwrite existing monitoring values when already set', async() => {
73
+ const value = {
74
+ monitoring: {
75
+ metrics: { enabled: true },
76
+ serviceMonitor: { enabled: true }
77
+ }
78
+ };
79
+
80
+ // The fetch logic only sets monitoring if it doesn't exist
81
+ // Since value.monitoring already exists, it should NOT be overwritten
82
+ // This test validates the conditional check in the component's fetch()
83
+ expect(value.monitoring).toBeDefined();
84
+ expect(value.monitoring.metrics.enabled).toBe(true);
85
+ expect(value.monitoring.serviceMonitor.enabled).toBe(true);
86
+ });
87
+ });
88
+
89
+ describe('monitoring checkboxes rendering', () => {
90
+ it('should render both monitoring checkboxes', async() => {
91
+ const value = {
92
+ monitoring: {
93
+ metrics: { enabled: false },
94
+ serviceMonitor: { enabled: false }
95
+ }
96
+ };
97
+ const wrapper = createWrapper({ value });
98
+
99
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
100
+
101
+ expect(checkboxes).toHaveLength(2);
102
+ });
103
+
104
+ it('should pass correct props to metrics checkbox', async() => {
105
+ const value = {
106
+ monitoring: {
107
+ metrics: { enabled: true },
108
+ serviceMonitor: { enabled: false }
109
+ }
110
+ };
111
+ const wrapper = createWrapper({ value });
112
+
113
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
114
+ const metricsCheckbox = checkboxes[0];
115
+
116
+ // The t() mock wraps strings in %, so we check for containment
117
+ expect(metricsCheckbox.attributes('label')).toContain('backupRestoreOperator.monitoring.enableMetrics');
118
+ expect(metricsCheckbox.attributes('value')).toBe('true');
119
+ });
120
+
121
+ it('should pass correct props to serviceMonitor checkbox', async() => {
122
+ const value = {
123
+ monitoring: {
124
+ metrics: { enabled: false },
125
+ serviceMonitor: { enabled: true }
126
+ }
127
+ };
128
+ const wrapper = createWrapper({ value });
129
+
130
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
131
+ const serviceMonitorCheckbox = checkboxes[1];
132
+
133
+ expect(serviceMonitorCheckbox.attributes('label')).toContain('backupRestoreOperator.monitoring.enableServiceMonitor');
134
+ expect(serviceMonitorCheckbox.attributes('value')).toBe('true');
135
+ });
136
+ });
137
+
138
+ describe('serviceMonitor checkbox disabled state', () => {
139
+ it('should set disabled to true when monitoring is not installed', async() => {
140
+ const value = {
141
+ monitoring: {
142
+ metrics: { enabled: false },
143
+ serviceMonitor: { enabled: false }
144
+ }
145
+ };
146
+ const wrapper = createWrapper({ value }, {}, false);
147
+
148
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
149
+ const serviceMonitorCheckbox = checkboxes[1];
150
+
151
+ expect(serviceMonitorCheckbox.attributes('disabled')).toBe('true');
152
+ });
153
+
154
+ it('should not set disabled when monitoring is installed', async() => {
155
+ const value = {
156
+ monitoring: {
157
+ metrics: { enabled: false },
158
+ serviceMonitor: { enabled: false }
159
+ }
160
+ };
161
+ const wrapper = createWrapper({ value }, {}, true);
162
+
163
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
164
+ const serviceMonitorCheckbox = checkboxes[1];
165
+
166
+ // When monitoring is installed, disabled should be 'false' (string)
167
+ expect(serviceMonitorCheckbox.attributes('disabled')).toBe('false');
168
+ });
169
+ });
170
+
171
+ describe('serviceMonitor checkbox tooltip', () => {
172
+ it('should show tooltip when monitoring is not installed', async() => {
173
+ const value = {
174
+ monitoring: {
175
+ metrics: { enabled: false },
176
+ serviceMonitor: { enabled: false }
177
+ }
178
+ };
179
+ const wrapper = createWrapper({ value }, {}, false);
180
+
181
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
182
+ const serviceMonitorCheckbox = checkboxes[1];
183
+
184
+ expect(serviceMonitorCheckbox.attributes('tooltip')).toContain('backupRestoreOperator.monitoring.serviceMonitorTooltip');
185
+ });
186
+
187
+ it('should not show tooltip when monitoring is installed', async() => {
188
+ const value = {
189
+ monitoring: {
190
+ metrics: { enabled: false },
191
+ serviceMonitor: { enabled: false }
192
+ }
193
+ };
194
+ const wrapper = createWrapper({ value }, {}, true);
195
+
196
+ const checkboxes = wrapper.findAllComponents({ name: 'Checkbox' });
197
+ const serviceMonitorCheckbox = checkboxes[1];
198
+
199
+ expect(serviceMonitorCheckbox.attributes('tooltip')).toBeFalsy();
200
+ });
201
+ });
202
+
203
+ describe('metrics checkbox', () => {
204
+ it('should never be disabled regardless of monitoring status', async() => {
205
+ const value = {
206
+ monitoring: {
207
+ metrics: { enabled: false },
208
+ serviceMonitor: { enabled: false }
209
+ }
210
+ };
211
+
212
+ // Test with monitoring not installed
213
+ const wrapperNotInstalled = createWrapper({ value }, {}, false);
214
+ const checkboxesNotInstalled = wrapperNotInstalled.findAllComponents({ name: 'Checkbox' });
215
+ const metricsCheckboxNotInstalled = checkboxesNotInstalled[0];
216
+
217
+ // The metrics checkbox does not have a disabled prop bound, so it should be 'false' or undefined
218
+ const disabledAttr = metricsCheckboxNotInstalled.attributes('disabled');
219
+
220
+ expect(disabledAttr === 'false' || disabledAttr === undefined).toBe(true);
221
+
222
+ // Test with monitoring installed
223
+ const wrapperInstalled = createWrapper({ value }, {}, true);
224
+ const checkboxesInstalled = wrapperInstalled.findAllComponents({ name: 'Checkbox' });
225
+ const metricsCheckboxInstalled = checkboxesInstalled[0];
226
+ const disabledAttrInstalled = metricsCheckboxInstalled.attributes('disabled');
227
+
228
+ expect(disabledAttrInstalled === 'false' || disabledAttrInstalled === undefined).toBe(true);
229
+ });
230
+ });
231
+
232
+ describe('monitoring section heading', () => {
233
+ it('should render the monitoring section heading', async() => {
234
+ const value = {
235
+ monitoring: {
236
+ metrics: { enabled: false },
237
+ serviceMonitor: { enabled: false }
238
+ }
239
+ };
240
+ const wrapper = createWrapper({ value });
241
+
242
+ const heading = wrapper.find('h3');
243
+
244
+ expect(heading.exists()).toBe(true);
245
+ expect(heading.text()).toContain('backupRestoreOperator.monitoring.label');
246
+ });
247
+ });
248
+ });
@@ -6,11 +6,13 @@ import { RadioGroup } from '@components/Form/Radio';
6
6
  import LabeledSelect from '@shell/components/form/LabeledSelect';
7
7
  import { LabeledInput } from '@components/Form/LabeledInput';
8
8
  import { Banner } from '@components/Banner';
9
- import { get } from '@shell/utils/object';
9
+ import { get, set } from '@shell/utils/object';
10
10
  import { allHash } from '@shell/utils/promise';
11
11
  import { STORAGE_CLASS, PV } from '@shell/config/types';
12
12
  import { mapGetters } from 'vuex';
13
13
  import { STORAGE } from '@shell/config/labels-annotations';
14
+ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
15
+ import { monitoringStatus } from '@shell/utils/monitoring';
14
16
 
15
17
  export default {
16
18
  emits: ['valid'],
@@ -22,7 +24,8 @@ export default {
22
24
  S3,
23
25
  LabeledInput,
24
26
  LabeledSelect,
25
- Banner
27
+ Banner,
28
+ Checkbox
26
29
  },
27
30
 
28
31
  props: {
@@ -39,6 +42,16 @@ export default {
39
42
  }
40
43
  },
41
44
 
45
+ created() {
46
+ // Initialize monitoring before template renders to avoid undefined access
47
+ if (!this.value.monitoring) {
48
+ set(this.value, 'monitoring', {
49
+ metrics: { enabled: false },
50
+ serviceMonitor: { enabled: false }
51
+ });
52
+ }
53
+ },
54
+
42
55
  async fetch() {
43
56
  const hash = await allHash({
44
57
  storageClasses: this.$store.dispatch('cluster/findAll', { type: STORAGE_CLASS }),
@@ -64,6 +77,8 @@ export default {
64
77
  },
65
78
 
66
79
  computed: {
80
+ ...monitoringStatus(),
81
+
67
82
  defaultStorageClass() {
68
83
  return this.storageClasses.filter((sc) => sc.metadata.annotations?.[STORAGE.DEFAULT_STORAGE_CLASS] && sc.metadata.annotations[STORAGE.DEFAULT_STORAGE_CLASS] !== 'false' )[0] || '';
69
84
  },
@@ -248,6 +263,24 @@ export default {
248
263
  </div>
249
264
  </div>
250
265
  </template>
266
+
267
+ <h3 class="mb-10 mt-10">
268
+ {{ t('backupRestoreOperator.monitoring.label') }}
269
+ </h3>
270
+ <div class="row monitoring-options">
271
+ <Checkbox
272
+ v-model:value="value.monitoring.metrics.enabled"
273
+ :label="t('backupRestoreOperator.monitoring.enableMetrics')"
274
+ :mode="mode"
275
+ />
276
+ <Checkbox
277
+ v-model:value="value.monitoring.serviceMonitor.enabled"
278
+ :label="t('backupRestoreOperator.monitoring.enableServiceMonitor')"
279
+ :disabled="!monitoringStatus.installed"
280
+ :tooltip="!monitoringStatus.installed ? t('backupRestoreOperator.monitoring.serviceMonitorTooltip') : null"
281
+ :mode="mode"
282
+ />
283
+ </div>
251
284
  </Tab>
252
285
  </Tabbed>
253
286
  </div>
@@ -257,4 +290,10 @@ export default {
257
290
  :deep() .radio-group.label>SPAN {
258
291
  font-size: 1em;
259
292
  }
293
+
294
+ .monitoring-options {
295
+ display: flex;
296
+ flex-direction: column;
297
+ gap: 4px;
298
+ }
260
299
  </style>
@@ -2,6 +2,7 @@
2
2
  import { mapGetters } from 'vuex';
3
3
  import { MANAGEMENT } from '@shell/config/types';
4
4
  import { SETTING } from '@shell/config/settings';
5
+ import { requireAsset } from '@shell/utils/require-asset';
5
6
 
6
7
  export default {
7
8
  props: {
@@ -72,9 +73,9 @@ export default {
72
73
  const themePrefix = this.theme === 'dark' ? 'dark/' : '';
73
74
 
74
75
  try {
75
- return require(`~shell/assets/images/pl/${ themePrefix }${ this.fileName }`);
76
+ return requireAsset(`~shell/assets/images/pl/${ themePrefix }${ this.fileName }`);
76
77
  } catch {
77
- return require(`~shell/assets/images/pl/${ this.fileName }`);
78
+ return requireAsset(`~shell/assets/images/pl/${ this.fileName }`);
78
79
  }
79
80
  },
80
81
 
@@ -95,7 +96,7 @@ export default {
95
96
  // csp, rgs, and federal map to SUSE, but have their own custom logos
96
97
  if (this.brandBase !== this.brand) {
97
98
  try {
98
- return require(`~shell/assets/brand/${ this.brandBase }/${ this.isDark ? 'dark/' : '' }${ this.fileName }`);
99
+ return requireAsset(`~shell/assets/brand/${ this.brandBase }/${ this.isDark ? 'dark/' : '' }${ this.fileName }`);
99
100
  } catch { }
100
101
  }
101
102
  }
@@ -125,11 +126,11 @@ export default {
125
126
  } else {
126
127
  if (this.isDark || this.dark) {
127
128
  try {
128
- return require(`~shell/assets/brand/${ this.brand }/dark/${ this.fileName }`);
129
+ return requireAsset(`~shell/assets/brand/${ this.brand }/dark/${ this.fileName }`);
129
130
  } catch {}
130
131
  }
131
132
  try {
132
- return require(`~shell/assets/brand/${ this.brand }/${ this.fileName }`);
133
+ return requireAsset(`~shell/assets/brand/${ this.brand }/${ this.fileName }`);
133
134
  } catch {}
134
135
 
135
136
  return this.defaultPathToBrandedImage;
@@ -54,11 +54,19 @@ export default {
54
54
  },
55
55
 
56
56
  /**
57
- * Reduce the vertial height by changed 'Used' for the resource name
57
+ * Reduce the vertical height by replacing 'Used'/usedLabel with the resource name
58
58
  */
59
59
  usedAsResourceName: {
60
- type: Boolean,
61
- defaut: false
60
+ type: Boolean,
61
+ default: false
62
+ },
63
+
64
+ /**
65
+ * Override the default "Used" label text (e.g. "Running" for pods).
66
+ */
67
+ usedLabel: {
68
+ type: String,
69
+ default: null
62
70
  }
63
71
  },
64
72
  computed: {
@@ -105,7 +113,7 @@ export default {
105
113
  <h4 v-if="usedAsResourceName">
106
114
  {{ resourceName }}
107
115
  </h4>
108
- <span v-else>{{ t('node.detail.glance.consumptionGauge.used') }}</span>
116
+ <span v-else>{{ usedLabel || t('node.detail.glance.consumptionGauge.used') }}</span>
109
117
  <span class="numbers-stats">
110
118
  {{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }}
111
119
  <span class="percentage"><i>/&nbsp;</i>{{ formattedPercentage }}</span>
@@ -38,7 +38,35 @@ export default {
38
38
  success-label="Copied!"
39
39
  error-label="Error Copying"
40
40
  v-bind="$attrs"
41
+ :success-color="$attrs['action-color'] || 'role-primary'"
42
+ :waiting-color="$attrs['action-color'] || 'role-primary'"
41
43
  :delay="2000"
42
44
  @click="clicked"
43
45
  />
44
46
  </template>
47
+
48
+ <style lang="scss" scoped>
49
+ .icon-btn {
50
+ min-height: 24px;
51
+ min-width: 24px;
52
+ justify-content: center;
53
+ }
54
+
55
+ .bg-transparent {
56
+ &:active {
57
+ background-color: var(--primary-keyboard-focus);
58
+ color: var(--primary-text);
59
+ }
60
+
61
+ &:focus-visible {
62
+ @include focus-outline;
63
+ }
64
+ }
65
+
66
+ .role-primary {
67
+ &:active {
68
+ background-color: var(--primary-keyboard-focus);
69
+ color: var(--primary-text);
70
+ }
71
+ }
72
+ </style>
@@ -75,6 +75,10 @@ export default {
75
75
  }
76
76
  }
77
77
 
78
+ &:active {
79
+ color: var(--primary-keyboard-focus);
80
+ }
81
+
78
82
  &.copied {
79
83
  pointer-events: none;
80
84
  color: var(--success);
@@ -782,6 +782,7 @@ export default {
782
782
  </div>
783
783
  <slot name="form-footer">
784
784
  <CruResourceFooter
785
+ v-if="!isView"
785
786
  class="cru__footer"
786
787
  :mode="mode"
787
788
  :is-form="showAsForm"
@@ -7,6 +7,7 @@ import { useStore } from 'vuex';
7
7
  import { computed } from 'vue';
8
8
 
9
9
  import { AnnouncementNotificationIconData } from '@shell/utils/dynamic-content/types';
10
+ import { requireAsset } from '@shell/utils/require-asset';
10
11
 
11
12
  type KeyValues = {
12
13
  [key: string]: string;
@@ -49,9 +50,9 @@ const src = computed(() => {
49
50
  const themePrefix = theme.value === 'dark' ? 'dark/' : '';
50
51
 
51
52
  try {
52
- return require(`~shell/assets/images/content/${ themePrefix }${ img }`);
53
+ return requireAsset(`~shell/assets/images/content/${ themePrefix }${ img }`);
53
54
  } catch {
54
- return require(`~shell/assets/images/content/${ img }`);
55
+ return requireAsset(`~shell/assets/images/content/${ img }`);
55
56
  }
56
57
  }
57
58
 
@@ -593,15 +593,12 @@ export default {
593
593
 
594
594
  .project-namespaces {
595
595
  & :deep() {
596
- .project-namespaces-table table {
597
- table-layout: fixed;
598
- }
599
-
600
596
  .project-name {
601
597
  line-height: 30px;
602
598
  }
603
599
 
604
600
  .project-bar {
601
+ contain: inline-size;
605
602
  display: flex;
606
603
  flex-direction: row;
607
604
  justify-content: space-between;