@rancher/shell 3.0.9-rc.5 → 3.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/images/providers/oci-open-containers.svg +22 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/themes/_dark.scss +2 -0
- package/assets/styles/themes/_light.scss +2 -0
- package/assets/styles/themes/_modern.scss +6 -0
- package/assets/translations/en-us.yaml +129 -25
- package/components/CruResource.vue +3 -1
- package/components/ExplorerProjectsNamespaces.vue +12 -12
- package/components/IconOrSvg.vue +61 -42
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
- package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
- package/components/Resource/Detail/ResourceRow.vue +2 -2
- package/components/ResourceList/index.vue +7 -4
- package/components/SortableTable/index.vue +2 -2
- package/components/Window/ContainerLogs.vue +48 -37
- package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
- package/components/fleet/FleetClusterTargets/index.vue +6 -1
- package/components/fleet/GitRepoAdvancedTab.vue +333 -0
- package/components/fleet/GitRepoMetadataTab.vue +43 -0
- package/components/fleet/GitRepoRepositoryTab.vue +101 -0
- package/components/fleet/GitRepoTargetTab.vue +77 -0
- package/components/fleet/HelmOpAdvancedTab.vue +247 -0
- package/components/fleet/HelmOpChartTab.vue +158 -0
- package/components/fleet/HelmOpMetadataTab.vue +46 -0
- package/components/fleet/HelmOpTargetTab.vue +84 -0
- package/components/fleet/HelmOpValuesTab.vue +147 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
- package/components/form/BannerSettings.vue +2 -2
- package/components/form/NodeScheduling.vue +81 -7
- package/components/form/NotificationSettings.vue +2 -2
- package/components/form/PodAffinity.vue +1 -36
- package/components/form/ResourceLabeledSelect.vue +8 -4
- package/components/form/ResourceQuota/Namespace.vue +30 -9
- package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
- package/components/form/ResourceQuota/Project.vue +140 -82
- package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
- package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
- package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
- package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
- package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
- package/components/form/SchedulingCustomization.vue +14 -6
- package/components/form/SelectOrCreateAuthSecret.vue +107 -18
- package/components/form/__tests__/NodeScheduling.test.ts +12 -9
- package/components/form/__tests__/PodAffinity.test.ts +21 -2
- package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
- package/components/formatter/ClusterLink.vue +8 -0
- package/components/formatter/SecretOrigin.vue +79 -0
- package/config/labels-annotations.js +7 -6
- package/config/pagination-table-headers.js +6 -4
- package/config/product/explorer.js +1 -11
- package/config/product/manager.js +0 -1
- package/config/query-params.js +3 -0
- package/config/settings.ts +15 -2
- package/config/table-headers.js +21 -17
- package/config/types.js +23 -8
- package/detail/fleet.cattle.io.cluster.vue +1 -1
- package/detail/workload/index.vue +11 -16
- package/dialog/DeactivateDriverDialog.vue +1 -1
- package/dialog/FeatureFlagListDialog.vue +1 -1
- package/dialog/Ipv6NetworkingDialog.vue +156 -0
- package/dialog/ScalePoolDownDialog.vue +2 -2
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -128
- package/edit/auth/oidc.vue +1 -1
- package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
- package/edit/fleet.cattle.io.gitrepo.vue +153 -283
- package/edit/fleet.cattle.io.helmop.vue +190 -332
- package/edit/management.cattle.io.project.vue +5 -42
- package/edit/management.cattle.io.setting.vue +6 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
- package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
- package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
- package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +114 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +167 -69
- package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +70 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +343 -0
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
- package/edit/secret/index.vue +1 -1
- package/edit/token.vue +68 -29
- package/edit/workload/__tests__/index.test.ts +2 -37
- package/edit/workload/index.vue +6 -2
- package/edit/workload/mixins/workload.js +0 -32
- package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
- package/list/management.cattle.io.setting.vue +13 -0
- package/list/provisioning.cattle.io.cluster.vue +50 -1
- package/list/secret.vue +4 -9
- package/list/service.vue +6 -8
- package/machine-config/amazonec2.vue +11 -4
- package/machine-config/components/EC2Networking.vue +46 -30
- package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
- package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
- package/machine-config/digitalocean.vue +3 -3
- package/models/__tests__/chart.test.ts +2 -2
- package/models/__tests__/namespace.test.ts +11 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
- package/models/__tests__/workload.test.ts +42 -1
- package/models/catalog.cattle.io.clusterrepo.js +30 -4
- package/models/chart.js +3 -3
- package/models/ext.cattle.io.token.js +48 -0
- package/models/kontainerdriver.js +2 -2
- package/models/namespace.js +7 -1
- package/models/nodedriver.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +28 -7
- package/models/secret.js +0 -17
- package/models/service.js +44 -1
- package/models/token.js +4 -0
- package/models/workload.js +12 -6
- package/package.json +1 -1
- package/pages/account/index.vue +96 -67
- package/pages/auth/setup.vue +5 -14
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +45 -18
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
- package/pages/c/_cluster/apps/charts/index.vue +82 -3
- package/pages/c/_cluster/apps/charts/install.vue +317 -42
- package/pages/c/_cluster/explorer/tools/index.vue +1 -1
- package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
- package/pages/c/_cluster/settings/index.vue +3 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
- package/plugins/dashboard-store/actions.js +3 -8
- package/plugins/dashboard-store/getters.js +7 -5
- package/plugins/dashboard-store/mutations.js +4 -1
- package/plugins/dashboard-store/resource-class.js +3 -3
- package/plugins/steve/__tests__/steve-class.test.ts +102 -141
- package/plugins/steve/steve-class.js +12 -3
- package/plugins/steve/steve-pagination-utils.ts +6 -2
- package/rancher-components/RcIcon/types.ts +2 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +72 -20
- package/store/prefs.js +3 -0
- package/types/aws-sdk.d.ts +121 -0
- package/types/resources/node.ts +15 -0
- package/types/shell/index.d.ts +537 -506
- package/types/store/pagination.types.ts +5 -5
- package/utils/__tests__/array.test.ts +1 -29
- package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
- package/utils/array.ts +0 -11
- package/utils/aws.ts +21 -0
- package/utils/cluster.js +22 -2
- package/utils/selector-typed.ts +1 -1
- package/utils/svg-filter.js +4 -3
- package/components/__tests__/ProjectRow.test.ts +0 -206
- package/components/form/ResourceQuota/ProjectRow.vue +0 -277
|
@@ -428,6 +428,33 @@ describe('class: Resource', () => {
|
|
|
428
428
|
});
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
+
describe('getter: _glance', () => {
|
|
432
|
+
it('should not throw when currentCluster or currentProduct is undefined', () => {
|
|
433
|
+
const resource = new Resource({
|
|
434
|
+
type: 'test',
|
|
435
|
+
metadata: { creationTimestamp: '2024-01-01T00:00:00Z' }
|
|
436
|
+
}, {
|
|
437
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
438
|
+
dispatch: jest.fn(),
|
|
439
|
+
rootGetters: {
|
|
440
|
+
'i18n/t': (key: string) => key,
|
|
441
|
+
currentCluster: undefined,
|
|
442
|
+
currentProduct: undefined,
|
|
443
|
+
'type-map/labelFor': () => 'Test',
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
expect(() => resource._glance).not.toThrow();
|
|
448
|
+
|
|
449
|
+
const glance = resource._glance;
|
|
450
|
+
const namespaceItem = glance.find((item: any) => item.name === 'namespace');
|
|
451
|
+
|
|
452
|
+
expect(namespaceItem.formatter).toBeUndefined();
|
|
453
|
+
expect(namespaceItem.formatterOpts.to.cluster).toBeUndefined();
|
|
454
|
+
expect(namespaceItem.formatterOpts.to.product).toBeUndefined();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
431
458
|
describe('getter: detailPageAdditionalActions', () => {
|
|
432
459
|
it('should return undefined by default', () => {
|
|
433
460
|
const resource = new Resource({ type: 'test-type' }, {
|
|
@@ -471,7 +471,7 @@ export default {
|
|
|
471
471
|
return findAllGetter(getters, type, opt);
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
console.log(`Find Page: [${ ctx.state.config.namespace }] ${ type }. Page: ${ opt.pagination.page }. Revision: ${ opt.revision || 'none' }. Size: ${ opt.pagination.pageSize }. Sort: ${ opt.pagination.sort
|
|
474
|
+
console.log(`Find Page: [${ ctx.state.config.namespace }] ${ type }. Page: ${ opt.pagination.page }. Revision: ${ opt.revision || 'none' }. Size: ${ opt.pagination.pageSize }. Sort: ${ opt.pagination.sort?.map((s) => s.field).join(', ') }`); // eslint-disable-line no-console
|
|
475
475
|
opt = opt || {};
|
|
476
476
|
opt.url = getters.urlFor(type, null, opt);
|
|
477
477
|
|
|
@@ -554,7 +554,6 @@ export default {
|
|
|
554
554
|
*/
|
|
555
555
|
async findLabelSelector(ctx, {
|
|
556
556
|
type,
|
|
557
|
-
context,
|
|
558
557
|
matching: {
|
|
559
558
|
namespace,
|
|
560
559
|
labelSelector
|
|
@@ -562,14 +561,10 @@ export default {
|
|
|
562
561
|
opt
|
|
563
562
|
}) {
|
|
564
563
|
const { getters, dispatch } = ctx;
|
|
565
|
-
const args = {
|
|
566
|
-
id: type,
|
|
567
|
-
context,
|
|
568
|
-
};
|
|
569
564
|
|
|
570
565
|
opt = opt || {};
|
|
571
566
|
|
|
572
|
-
if (getters[`paginationEnabled`]?.(
|
|
567
|
+
if (getters[`paginationEnabled`]?.()) {
|
|
573
568
|
if (isLabelSelectorEmpty(labelSelector)) {
|
|
574
569
|
throw new Error(`labelSelector must not be empty when using findLabelSelector (avoid fetching all resources)`);
|
|
575
570
|
}
|
|
@@ -581,7 +576,7 @@ export default {
|
|
|
581
576
|
...opt,
|
|
582
577
|
namespaced: namespace,
|
|
583
578
|
pagination: new FilterArgs({ labelSelector }),
|
|
584
|
-
transient: opt?.transient !== undefined ? opt.transient : false // Call this out explicitly here, as by default findX methods
|
|
579
|
+
transient: opt?.transient !== undefined ? opt.transient : false // Call this out explicitly here, as by default findX methods are usually cached AND watched
|
|
585
580
|
}
|
|
586
581
|
});
|
|
587
582
|
}
|
|
@@ -144,8 +144,7 @@ export default {
|
|
|
144
144
|
page?.namespace === namespace &&
|
|
145
145
|
page?.pagination?.filters?.length === 0 &&
|
|
146
146
|
page?.pagination.labelSelector &&
|
|
147
|
-
selector === labelSelectorToSelector(page?.pagination.labelSelector
|
|
148
|
-
)
|
|
147
|
+
selector === labelSelectorToSelector(page?.pagination.labelSelector)
|
|
149
148
|
) {
|
|
150
149
|
return getters.all(type);
|
|
151
150
|
}
|
|
@@ -155,11 +154,14 @@ export default {
|
|
|
155
154
|
return getters.all(type);
|
|
156
155
|
}
|
|
157
156
|
|
|
157
|
+
// Does the store contain a page, assume it's a subset of all resources still covering applicable resources, and
|
|
158
|
+
// apply labelSelector to get actual result
|
|
158
159
|
if (getters['havePage'](type)) {
|
|
159
|
-
return getters.
|
|
160
|
+
return getters.matching( type, selector, namespace );
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
// Does the store
|
|
163
|
+
// Does the store contain all resources, if so
|
|
164
|
+
// apply labelSelector to get actual result
|
|
163
165
|
if (getters['haveAll'](type)) {
|
|
164
166
|
return getters.matching( type, selector, namespace );
|
|
165
167
|
}
|
|
@@ -177,7 +179,7 @@ export default {
|
|
|
177
179
|
|
|
178
180
|
// Filter first by namespace if one is provided, since this is efficient
|
|
179
181
|
if (namespace && typeof namespace === 'string') {
|
|
180
|
-
matching = type === POD ? getters['podsByNamespace'](namespace) : matching.filter((obj) => obj.namespace === namespace);
|
|
182
|
+
matching = type === POD && !selector ? getters['podsByNamespace'](namespace) : matching.filter((obj) => obj.namespace === namespace);
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
garbageCollect.gcUpdateLastAccessed({
|
|
@@ -440,7 +440,10 @@ export default {
|
|
|
440
440
|
cache.map.set(proxies[i][keyField], proxies[i]);
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
cache.
|
|
443
|
+
cache.havePage = undefined;
|
|
444
|
+
cache.haveNamespace = undefined;
|
|
445
|
+
cache.haveSelector = { [selector]: true };
|
|
446
|
+
cache.haveAll = undefined;
|
|
444
447
|
cache.revision = revision || 0;
|
|
445
448
|
},
|
|
446
449
|
|
|
@@ -1956,12 +1956,12 @@ export default class Resource {
|
|
|
1956
1956
|
{
|
|
1957
1957
|
name: 'namespace',
|
|
1958
1958
|
label: this.t('component.resource.detail.glance.namespace'),
|
|
1959
|
-
formatter: 'Link',
|
|
1959
|
+
formatter: this.$rootGetters['currentProduct']?.id && this.$rootGetters['currentCluster']?.id ? 'Link' : undefined,
|
|
1960
1960
|
formatterOpts: {
|
|
1961
1961
|
to: {
|
|
1962
1962
|
name: `c-cluster-product-resource-id`,
|
|
1963
|
-
product: this.$rootGetters['currentProduct']
|
|
1964
|
-
cluster: this.$rootGetters['currentCluster']
|
|
1963
|
+
product: this.$rootGetters['currentProduct']?.id,
|
|
1964
|
+
cluster: this.$rootGetters['currentCluster']?.id,
|
|
1965
1965
|
resource: this.type
|
|
1966
1966
|
},
|
|
1967
1967
|
row: {},
|
|
@@ -77,150 +77,111 @@ describe('class: Steve', () => {
|
|
|
77
77
|
expect(parentProcessSaveResponse).toHaveBeenCalledWith(response);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'growl/success',
|
|
141
|
-
{
|
|
142
|
-
title: 'CustomResourceDefinition created',
|
|
143
|
-
message: 'Resource simple-id created successfully',
|
|
144
|
-
timeout: 3000
|
|
145
|
-
},
|
|
146
|
-
{ root: true }
|
|
147
|
-
);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should not show growl notification for non-201 status', () => {
|
|
151
|
-
const mockDispatch = jest.fn();
|
|
152
|
-
const mockT = jest.fn();
|
|
153
|
-
const mockRootGetters = { 'i18n/t': mockT };
|
|
154
|
-
const steve = new Steve(customResource, {
|
|
155
|
-
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
156
|
-
dispatch: mockDispatch,
|
|
157
|
-
rootGetters: mockRootGetters,
|
|
80
|
+
describe('growl notifications', () => {
|
|
81
|
+
describe('should show growl notification', () => {
|
|
82
|
+
it.each([
|
|
83
|
+
{
|
|
84
|
+
name: 'for autogenerated names on 201 status',
|
|
85
|
+
response: {
|
|
86
|
+
_status: 201,
|
|
87
|
+
metadata: { generateName: 'test-generated-' },
|
|
88
|
+
id: 'default/test-generated-abc123'
|
|
89
|
+
},
|
|
90
|
+
expectedResource: 'CustomResourceDefinition',
|
|
91
|
+
expectedId: 'test-generated-abc123'
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'for autogenerated names without namespace',
|
|
95
|
+
response: {
|
|
96
|
+
_status: 201,
|
|
97
|
+
metadata: { generateName: 'simple-' },
|
|
98
|
+
id: 'simple-id'
|
|
99
|
+
},
|
|
100
|
+
expectedResource: 'CustomResourceDefinition',
|
|
101
|
+
expectedId: 'simple-id'
|
|
102
|
+
}
|
|
103
|
+
])('$name', ({ response, expectedResource, expectedId }) => {
|
|
104
|
+
const mockDispatch = jest.fn();
|
|
105
|
+
const mockT = jest.fn((key, params) => {
|
|
106
|
+
if (key === 'generic.autogeneratedCreated.title') {
|
|
107
|
+
return `${ params.resource } created`;
|
|
108
|
+
}
|
|
109
|
+
if (key === 'generic.autogeneratedCreated.message') {
|
|
110
|
+
return `Resource ${ params.id } created successfully`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return key;
|
|
114
|
+
});
|
|
115
|
+
const mockRootGetters = { 'i18n/t': mockT };
|
|
116
|
+
const steve = new Steve(customResource, {
|
|
117
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
118
|
+
dispatch: mockDispatch,
|
|
119
|
+
rootGetters: mockRootGetters,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Mock typeDisplay to return the expected resource label
|
|
123
|
+
Object.defineProperty(steve, 'typeDisplay', { get: jest.fn(() => expectedResource) });
|
|
124
|
+
|
|
125
|
+
steve.processSaveResponse(response);
|
|
126
|
+
|
|
127
|
+
expect(mockT).toHaveBeenCalledWith('generic.autogeneratedCreated.title', { resource: expectedResource });
|
|
128
|
+
expect(mockT).toHaveBeenCalledWith('generic.autogeneratedCreated.message', { id: expectedId, resource: expectedResource });
|
|
129
|
+
|
|
130
|
+
expect(mockDispatch).toHaveBeenCalledWith(
|
|
131
|
+
'growl/success',
|
|
132
|
+
{
|
|
133
|
+
title: `${ expectedResource } created`,
|
|
134
|
+
message: `Resource ${ expectedId } created successfully`,
|
|
135
|
+
timeout: 3000
|
|
136
|
+
},
|
|
137
|
+
{ root: true }
|
|
138
|
+
);
|
|
139
|
+
});
|
|
158
140
|
});
|
|
159
141
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
142
|
+
describe('should not show growl notification', () => {
|
|
143
|
+
it.each([
|
|
144
|
+
{
|
|
145
|
+
name: 'for non-201 status',
|
|
146
|
+
response: {
|
|
147
|
+
_status: 200,
|
|
148
|
+
metadata: { generateName: 'test-generated-' },
|
|
149
|
+
id: 'default/test-generated-abc123'
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'without generateName',
|
|
154
|
+
response: {
|
|
155
|
+
_status: 201,
|
|
156
|
+
id: 'default/test-regular-name'
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'without id',
|
|
161
|
+
response: {
|
|
162
|
+
_status: 201,
|
|
163
|
+
metadata: { generateName: 'test-generated-' }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
])('$name', ({ response }) => {
|
|
167
|
+
const mockDispatch = jest.fn();
|
|
168
|
+
const mockT = jest.fn();
|
|
169
|
+
const mockRootGetters = { 'i18n/t': mockT };
|
|
170
|
+
const steve = new Steve(customResource, {
|
|
171
|
+
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
172
|
+
dispatch: mockDispatch,
|
|
173
|
+
rootGetters: mockRootGetters,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
steve.processSaveResponse(response);
|
|
177
|
+
|
|
178
|
+
expect(mockDispatch).not.toHaveBeenCalledWith(
|
|
179
|
+
'growl/success',
|
|
180
|
+
expect.any(Object),
|
|
181
|
+
{ root: true }
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
184
|
});
|
|
185
|
-
|
|
186
|
-
const response = {
|
|
187
|
-
_status: 201,
|
|
188
|
-
id: 'default/test-regular-name'
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
steve.processSaveResponse(response);
|
|
192
|
-
|
|
193
|
-
expect(mockT).not.toHaveBeenCalled();
|
|
194
|
-
expect(mockDispatch).not.toHaveBeenCalledWith(
|
|
195
|
-
'growl/success',
|
|
196
|
-
expect.any(Object),
|
|
197
|
-
{ root: true }
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('should not show growl notification without id', () => {
|
|
202
|
-
const mockDispatch = jest.fn();
|
|
203
|
-
const mockT = jest.fn();
|
|
204
|
-
const mockRootGetters = { 'i18n/t': mockT };
|
|
205
|
-
const steve = new Steve(customResource, {
|
|
206
|
-
getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
|
|
207
|
-
dispatch: mockDispatch,
|
|
208
|
-
rootGetters: mockRootGetters,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const response = {
|
|
212
|
-
_status: 201,
|
|
213
|
-
metadata: { generateName: 'test-generated-' }
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
steve.processSaveResponse(response);
|
|
217
|
-
|
|
218
|
-
expect(mockT).not.toHaveBeenCalled();
|
|
219
|
-
expect(mockDispatch).not.toHaveBeenCalledWith(
|
|
220
|
-
'growl/success',
|
|
221
|
-
expect.any(Object),
|
|
222
|
-
{ root: true }
|
|
223
|
-
);
|
|
224
185
|
});
|
|
225
186
|
});
|
|
226
187
|
});
|
|
@@ -64,19 +64,28 @@ export default class SteveModel extends HybridModel {
|
|
|
64
64
|
return this.$getters['paginationEnabled'](this.type);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Override the default save response processing to conditionally show a growl when a resource is created with an autogenerated name.
|
|
69
|
+
* The growl will display the resource type and the generated name, providing immediate feedback to the user about the creation of the resource.
|
|
70
|
+
*
|
|
71
|
+
* @param {*} res
|
|
72
|
+
*/
|
|
67
73
|
processSaveResponse(res) {
|
|
68
74
|
super.processSaveResponse(res);
|
|
69
75
|
|
|
70
76
|
// Conditionally show the growl for autogenerated names
|
|
71
77
|
if (res && res._status === 201 && res.metadata?.generateName && res.id) {
|
|
72
|
-
|
|
78
|
+
// Split to remove the namespace if present (default/generated-xxx)
|
|
73
79
|
const nameOnly = res.id.split('/').pop();
|
|
74
80
|
|
|
75
81
|
// Avoid showing the growl without the ID.
|
|
76
82
|
if (nameOnly.length > 0) {
|
|
83
|
+
const resource = this.typeDisplay;
|
|
84
|
+
|
|
85
|
+
// Show the growl with the resource type and generated name
|
|
77
86
|
this.$dispatch('growl/success', {
|
|
78
|
-
title: this.t('generic.autogeneratedCreated.title', { resource
|
|
79
|
-
message: this.t('generic.autogeneratedCreated.message', { id: nameOnly }),
|
|
87
|
+
title: this.t('generic.autogeneratedCreated.title', { resource }),
|
|
88
|
+
message: this.t('generic.autogeneratedCreated.message', { id: nameOnly, resource }),
|
|
80
89
|
timeout: 3000
|
|
81
90
|
}, { root: true });
|
|
82
91
|
}
|
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
INGRESS,
|
|
16
16
|
WORKLOAD_TYPES,
|
|
17
17
|
HPA,
|
|
18
|
-
SECRET
|
|
18
|
+
SECRET,
|
|
19
|
+
EXT
|
|
19
20
|
} from '@shell/config/types';
|
|
20
21
|
import { CAPI as CAPI_LAB_AND_ANO, CATTLE_PUBLIC_ENDPOINTS, STORAGE, UI_PROJECT_SECRET_COPY } from '@shell/config/labels-annotations';
|
|
21
22
|
import { Schema } from '@shell/plugins/steve/schema';
|
|
@@ -234,6 +235,8 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
234
235
|
[NODE]: [
|
|
235
236
|
{ field: 'status.nodeInfo.kubeletVersion' },
|
|
236
237
|
{ field: 'status.nodeInfo.operatingSystem' },
|
|
238
|
+
{ field: 'spec.taints.key' },
|
|
239
|
+
{ field: 'status.addresses.type' },
|
|
237
240
|
],
|
|
238
241
|
[POD]: [
|
|
239
242
|
{ field: 'spec.containers.image' },
|
|
@@ -765,7 +768,8 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStores = {
|
|
|
765
768
|
{ resource: MANAGEMENT.CLUSTER, context: ['side-bar'] },
|
|
766
769
|
{ resource: CATALOG.APP, context: ['branding'] },
|
|
767
770
|
SECRET,
|
|
768
|
-
CAPI.MACHINE_SET
|
|
771
|
+
CAPI.MACHINE_SET,
|
|
772
|
+
EXT.TOKEN
|
|
769
773
|
],
|
|
770
774
|
generic: false,
|
|
771
775
|
}
|
|
@@ -6,6 +6,8 @@ import LazyImage from '@shell/components/LazyImage.vue';
|
|
|
6
6
|
import { DropdownOption } from '@components/RcDropdown/types';
|
|
7
7
|
import ActionMenu from '@shell/components/ActionMenuShell.vue';
|
|
8
8
|
import RcItemCardAction from './RcItemCardAction';
|
|
9
|
+
import RcIcon from '@components/RcIcon/RcIcon.vue';
|
|
10
|
+
import { RcIconType } from '@components/RcIcon/types';
|
|
9
11
|
|
|
10
12
|
const store = useStore();
|
|
11
13
|
const { t } = useI18n(store);
|
|
@@ -25,11 +27,11 @@ type Label = {
|
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Represents an image used in the card.
|
|
30
|
+
* Can be either a traditional image (src) or an icon (icon).
|
|
28
31
|
*/
|
|
29
|
-
type Image =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
32
|
+
type Image =
|
|
33
|
+
| { src?: never, icon: RcIconType; alt: Label; }
|
|
34
|
+
| { src: string; icon?: never, alt: Label; };
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* Optional pill badge, typically used to highlight a tag or state.
|
|
@@ -113,6 +115,12 @@ interface RcItemCardProps {
|
|
|
113
115
|
/** Makes the card clickable and emits 'card-click' on click/enter/space */
|
|
114
116
|
clickable?: boolean;
|
|
115
117
|
|
|
118
|
+
/** The card will have same style as hover clickable with the blue border when selected */
|
|
119
|
+
selected?: boolean;
|
|
120
|
+
|
|
121
|
+
/** Disables the card, preventing clicks and keyboard interaction */
|
|
122
|
+
disabled?: boolean;
|
|
123
|
+
|
|
116
124
|
role?: 'link' | 'button' | undefined;
|
|
117
125
|
}
|
|
118
126
|
|
|
@@ -131,6 +139,10 @@ const emit = defineEmits<{(e: 'card-click', value?: ItemValue): void; (e: 'actio
|
|
|
131
139
|
* which then gets used to ignore 'card-click'
|
|
132
140
|
*/
|
|
133
141
|
function _handleCardClick(e: MouseEvent | KeyboardEvent) {
|
|
142
|
+
if (props.disabled) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
134
146
|
const interactiveSelector = '[item-card-action]';
|
|
135
147
|
|
|
136
148
|
// Prevent card click if the user clicks on an inner actionable element like repo, category, or tag
|
|
@@ -162,8 +174,8 @@ const statusTooltips = computed(() => props.header.statuses?.map((status) => lab
|
|
|
162
174
|
|
|
163
175
|
const cardMeta = computed(() => ({
|
|
164
176
|
ariaLabel: props.clickable ? t('itemCard.ariaLabel.clickable', { cardTitle: labelText(props.header.title) }) : undefined,
|
|
165
|
-
tabIndex: props.clickable ? '0' : undefined,
|
|
166
|
-
role: props.role ?? (props.clickable ? 'button' : undefined),
|
|
177
|
+
tabIndex: props.clickable && !props.disabled ? '0' : undefined,
|
|
178
|
+
role: props.role ?? (props.clickable && !props.disabled ? 'button' : undefined),
|
|
167
179
|
actionMenuLabel: props.actions && t('itemCard.actionMenu.label', { cardTitle: labelText(props.header.title) }),
|
|
168
180
|
}));
|
|
169
181
|
|
|
@@ -177,7 +189,7 @@ const cursorValue = computed(() => props.clickable ? 'pointer' : 'auto');
|
|
|
177
189
|
:data-testid="`item-card-${id}`"
|
|
178
190
|
:class="{
|
|
179
191
|
'clickable':
|
|
180
|
-
clickable
|
|
192
|
+
clickable && !disabled,'selected': selected, 'disabled': disabled
|
|
181
193
|
}"
|
|
182
194
|
@click="_handleCardClick"
|
|
183
195
|
>
|
|
@@ -190,11 +202,19 @@ const cursorValue = computed(() => props.clickable ? 'pointer' : 'auto');
|
|
|
190
202
|
:class="['item-card-image', variant]"
|
|
191
203
|
data-testid="item-card-image"
|
|
192
204
|
>
|
|
193
|
-
<
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
<template v-if="image.icon">
|
|
206
|
+
<RcIcon
|
|
207
|
+
:type="image.icon"
|
|
208
|
+
size="xxlarge"
|
|
209
|
+
/>
|
|
210
|
+
</template>
|
|
211
|
+
<template v-else-if="image.src">
|
|
212
|
+
<LazyImage
|
|
213
|
+
:src="image.src"
|
|
214
|
+
:alt="imageAlt"
|
|
215
|
+
:style="{'width': '40px', 'height': '40px', 'object-fit': 'contain'}"
|
|
216
|
+
/>
|
|
217
|
+
</template>
|
|
198
218
|
</div>
|
|
199
219
|
</slot>
|
|
200
220
|
<slot name="item-card-pill">
|
|
@@ -229,11 +249,19 @@ const cursorValue = computed(() => props.clickable ? 'pointer' : 'auto');
|
|
|
229
249
|
:class="['item-card-image', variant]"
|
|
230
250
|
data-testid="item-card-image"
|
|
231
251
|
>
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
252
|
+
<template v-if="image.icon">
|
|
253
|
+
<RcIcon
|
|
254
|
+
:type="image.icon"
|
|
255
|
+
size="xlarge"
|
|
256
|
+
/>
|
|
257
|
+
</template>
|
|
258
|
+
<template v-else-if="image.src">
|
|
259
|
+
<LazyImage
|
|
260
|
+
:src="image.src"
|
|
261
|
+
:alt="imageAlt"
|
|
262
|
+
:style="{'width': '32px', 'height': '32px', 'object-fit': 'contain'}"
|
|
263
|
+
/>
|
|
264
|
+
</template>
|
|
237
265
|
</div>
|
|
238
266
|
</slot>
|
|
239
267
|
</template>
|
|
@@ -286,7 +314,10 @@ const cursorValue = computed(() => props.clickable ? 'pointer' : 'auto');
|
|
|
286
314
|
</div>
|
|
287
315
|
</div>
|
|
288
316
|
|
|
289
|
-
<slot name="item-card-sub-header"
|
|
317
|
+
<slot name="item-card-sub-header">
|
|
318
|
+
<!-- DIV added to add the gap if the sub-header is not provided -->
|
|
319
|
+
<div />
|
|
320
|
+
</slot>
|
|
290
321
|
|
|
291
322
|
<template v-if="$slots['item-card-content']">
|
|
292
323
|
<slot name="item-card-content">
|
|
@@ -324,10 +355,26 @@ $image-medium-box-width: 48px;
|
|
|
324
355
|
background: var(--body-bg);
|
|
325
356
|
cursor: v-bind(cursorValue);
|
|
326
357
|
|
|
327
|
-
&.clickable:hover
|
|
358
|
+
&.clickable:hover,
|
|
359
|
+
&.selected {
|
|
328
360
|
border-color: var(--primary);
|
|
329
361
|
}
|
|
330
362
|
|
|
363
|
+
&.disabled {
|
|
364
|
+
cursor: not-allowed;
|
|
365
|
+
background-color: var(--disabled-bg);
|
|
366
|
+
color: var(--disabled-text);
|
|
367
|
+
|
|
368
|
+
.item-card-image {
|
|
369
|
+
background-color: var(--disabled-bg);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.item-card-header-title,
|
|
373
|
+
.item-card-content {
|
|
374
|
+
color: var(--disabled-text);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
331
378
|
&:has(.item-card-header-left:focus-visible) {
|
|
332
379
|
border-color: var(--primary);
|
|
333
380
|
@include focus-outline;
|
|
@@ -344,8 +391,9 @@ $image-medium-box-width: 48px;
|
|
|
344
391
|
display: flex;
|
|
345
392
|
align-items: center;
|
|
346
393
|
justify-content: center;
|
|
347
|
-
background:
|
|
394
|
+
background: var(--rc-image-bg);
|
|
348
395
|
border-radius: var(--border-radius);
|
|
396
|
+
color: var(--rc-image-color);
|
|
349
397
|
|
|
350
398
|
&.small {
|
|
351
399
|
width: 32px;
|
|
@@ -362,6 +410,10 @@ $image-medium-box-width: 48px;
|
|
|
362
410
|
height: 24px;
|
|
363
411
|
color: var(--body-text);
|
|
364
412
|
|
|
413
|
+
&.small {
|
|
414
|
+
height: 32px;
|
|
415
|
+
}
|
|
416
|
+
|
|
365
417
|
&-left,
|
|
366
418
|
&-right {
|
|
367
419
|
display: flex;
|
package/store/prefs.js
CHANGED
|
@@ -122,6 +122,9 @@ export const READ_SUPPORT_NOTICE = create('read-support-notice', '', { parseJSON
|
|
|
122
122
|
export const READ_UPCOMING_SUPPORT_NOTICE = create('read-upcoming-support-notice', '', { parseJSON });
|
|
123
123
|
export const READ_ANNOUNCEMENTS = create('read-announcements', '', { parseJSON });
|
|
124
124
|
|
|
125
|
+
// Hidden banners
|
|
126
|
+
export const HIDE_SUSE_APP_COLLECTION_REPO_BANNER = create('hide-suse-app-collection-repo-banner', false);
|
|
127
|
+
|
|
125
128
|
// --------------------
|
|
126
129
|
|
|
127
130
|
const cookiePrefix = 'R_';
|