@rancher/shell 3.0.9-rc.5 → 3.0.9-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.
- 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/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/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/NodeScheduling.vue +81 -7
- 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/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/workload/index.vue +11 -16
- package/dialog/DeactivateDriverDialog.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/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 +112 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -72
- 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 +55 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -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__/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/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/__tests__/install.test.ts +4 -1
- package/pages/c/_cluster/apps/charts/index.vue +93 -4
- package/pages/c/_cluster/apps/charts/install.vue +317 -42
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
- package/pages/c/_cluster/settings/index.vue +3 -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 +64 -19
- 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 +536 -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/components/__tests__/ProjectRow.test.ts +0 -206
- package/components/form/ResourceQuota/ProjectRow.vue +0 -277
|
@@ -47,6 +47,7 @@ describe('dashboard-store: getters', () => {
|
|
|
47
47
|
expect(urlForGetter('typeFoo', 'idBar')).toBe('protocol/urlFoo/idBar');
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
|
+
|
|
50
51
|
describe('dashboard-store > getters > urlOptions', () => {
|
|
51
52
|
// we're not testing function output based off of state or getter inputs here since they are dependencies
|
|
52
53
|
const state = { config: { baseUrl: 'protocol' } };
|
|
@@ -97,4 +98,111 @@ describe('dashboard-store: getters', () => {
|
|
|
97
98
|
expect(urlOptionsGetter('foo', { sortBy: 'bar', sortOrder: 'baz' })).toBe('foo');
|
|
98
99
|
});
|
|
99
100
|
});
|
|
101
|
+
|
|
102
|
+
describe('dashboard-store > getters > matchingLabelSelector', () => {
|
|
103
|
+
const { matchingLabelSelector } = getters;
|
|
104
|
+
const labelSelector = { matchLabels: { foo: 'bar' } };
|
|
105
|
+
const selectorString = 'foo=bar';
|
|
106
|
+
const type = 'pod';
|
|
107
|
+
const namespace = 'default';
|
|
108
|
+
const allResources = [{ id: '1' }];
|
|
109
|
+
const matchingResources = [{ id: '1' }];
|
|
110
|
+
|
|
111
|
+
it('returns all resources if store has a VAI page matching the selector', () => {
|
|
112
|
+
const state = {};
|
|
113
|
+
const rootState = {};
|
|
114
|
+
const gettersMock = {
|
|
115
|
+
normalizeType: jest.fn((t) => t),
|
|
116
|
+
havePage: jest.fn().mockReturnValue({
|
|
117
|
+
request: {
|
|
118
|
+
namespace,
|
|
119
|
+
pagination: {
|
|
120
|
+
filters: [],
|
|
121
|
+
labelSelector
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}),
|
|
125
|
+
all: jest.fn().mockReturnValue(allResources),
|
|
126
|
+
haveSelector: jest.fn(),
|
|
127
|
+
haveAll: jest.fn(),
|
|
128
|
+
matching: jest.fn(),
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
|
|
132
|
+
|
|
133
|
+
expect(result).toStrictEqual(allResources);
|
|
134
|
+
expect(gettersMock.all).toHaveBeenCalledWith(type);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('returns all resources if store has the specific selector cached', () => {
|
|
138
|
+
const state = {};
|
|
139
|
+
const rootState = {};
|
|
140
|
+
const gettersMock = {
|
|
141
|
+
normalizeType: jest.fn((t) => t),
|
|
142
|
+
havePage: jest.fn().mockReturnValue(null),
|
|
143
|
+
all: jest.fn().mockReturnValue(allResources),
|
|
144
|
+
haveSelector: jest.fn().mockReturnValue(true),
|
|
145
|
+
haveAll: jest.fn(),
|
|
146
|
+
matching: jest.fn(),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
|
|
150
|
+
|
|
151
|
+
expect(result).toStrictEqual(allResources);
|
|
152
|
+
expect(gettersMock.haveSelector).toHaveBeenCalledWith(type, selectorString);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns matching resources if store has a page (subset)', () => {
|
|
156
|
+
const state = {};
|
|
157
|
+
const rootState = {};
|
|
158
|
+
const gettersMock = {
|
|
159
|
+
normalizeType: jest.fn((t) => t),
|
|
160
|
+
havePage: jest.fn().mockReturnValue({ request: {} }), // Truthy, but doesn't match first if
|
|
161
|
+
all: jest.fn(),
|
|
162
|
+
haveSelector: jest.fn().mockReturnValue(false),
|
|
163
|
+
haveAll: jest.fn(),
|
|
164
|
+
matching: jest.fn().mockReturnValue(matchingResources),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
|
|
168
|
+
|
|
169
|
+
expect(result).toStrictEqual(matchingResources);
|
|
170
|
+
expect(gettersMock.matching).toHaveBeenCalledWith(type, selectorString, namespace);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('returns matching resources if store has all resources', () => {
|
|
174
|
+
const state = {};
|
|
175
|
+
const rootState = {};
|
|
176
|
+
const gettersMock = {
|
|
177
|
+
normalizeType: jest.fn((t) => t),
|
|
178
|
+
havePage: jest.fn().mockReturnValue(null),
|
|
179
|
+
all: jest.fn(),
|
|
180
|
+
haveSelector: jest.fn().mockReturnValue(false),
|
|
181
|
+
haveAll: jest.fn().mockReturnValue(true),
|
|
182
|
+
matching: jest.fn().mockReturnValue(matchingResources),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
|
|
186
|
+
|
|
187
|
+
expect(result).toStrictEqual(matchingResources);
|
|
188
|
+
expect(gettersMock.matching).toHaveBeenCalledWith(type, selectorString, namespace);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('returns empty array if no conditions met', () => {
|
|
192
|
+
const state = {};
|
|
193
|
+
const rootState = {};
|
|
194
|
+
const gettersMock = {
|
|
195
|
+
normalizeType: jest.fn((t) => t),
|
|
196
|
+
havePage: jest.fn().mockReturnValue(null),
|
|
197
|
+
all: jest.fn(),
|
|
198
|
+
haveSelector: jest.fn().mockReturnValue(false),
|
|
199
|
+
haveAll: jest.fn().mockReturnValue(false),
|
|
200
|
+
matching: jest.fn(),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const result = matchingLabelSelector(state, gettersMock, rootState)(type, labelSelector, namespace);
|
|
204
|
+
|
|
205
|
+
expect(result).toStrictEqual([]);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
100
208
|
});
|
|
@@ -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
|
}
|