@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
package/edit/token.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
import day from 'dayjs';
|
|
4
4
|
import sortBy from 'lodash/sortBy';
|
|
5
|
-
import { MANAGEMENT,
|
|
5
|
+
import { MANAGEMENT, EXT } from '@shell/config/types';
|
|
6
6
|
import { Banner } from '@components/Banner';
|
|
7
7
|
import DetailText from '@shell/components/DetailText';
|
|
8
8
|
import Footer from '@shell/components/form/Footer';
|
|
@@ -14,6 +14,7 @@ import CreateEditView from '@shell/mixins/create-edit-view';
|
|
|
14
14
|
import { diffFrom } from '@shell/utils/time';
|
|
15
15
|
import { filterHiddenLocalCluster, filterOnlyKubernetesClusters } from '@shell/utils/cluster';
|
|
16
16
|
import { SETTING } from '@shell/config/settings';
|
|
17
|
+
import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
|
|
17
18
|
|
|
18
19
|
export default {
|
|
19
20
|
components: {
|
|
@@ -24,6 +25,7 @@ export default {
|
|
|
24
25
|
LabeledSelect,
|
|
25
26
|
RadioGroup,
|
|
26
27
|
Select,
|
|
28
|
+
Checkbox,
|
|
27
29
|
},
|
|
28
30
|
|
|
29
31
|
mixins: [CreateEditView],
|
|
@@ -41,7 +43,11 @@ export default {
|
|
|
41
43
|
|
|
42
44
|
return {
|
|
43
45
|
errors: null,
|
|
46
|
+
user: null,
|
|
44
47
|
form: {
|
|
48
|
+
enabled: true,
|
|
49
|
+
description: '',
|
|
50
|
+
clusterName: '',
|
|
45
51
|
expiryType: 'never',
|
|
46
52
|
customExpiry: 0,
|
|
47
53
|
customExpiryUnits: 'minute',
|
|
@@ -51,11 +57,13 @@ export default {
|
|
|
51
57
|
accessKey: '',
|
|
52
58
|
secretKey: '',
|
|
53
59
|
maxTTL,
|
|
60
|
+
ttl: ''
|
|
54
61
|
};
|
|
55
62
|
},
|
|
56
63
|
|
|
57
64
|
computed: {
|
|
58
65
|
...mapGetters({ t: 'i18n/t' }),
|
|
66
|
+
|
|
59
67
|
scopes() {
|
|
60
68
|
const all = this.$store.getters['management/all'](MANAGEMENT.CLUSTER);
|
|
61
69
|
const kubeClusters = filterHiddenLocalCluster(filterOnlyKubernetesClusters(all, this.$store), this.$store);
|
|
@@ -71,16 +79,20 @@ export default {
|
|
|
71
79
|
const options = ['never', 'day', 'month', 'year', 'custom'];
|
|
72
80
|
let opts = options.map((opt) => ({ value: opt, label: this.t(`accountAndKeys.apiKeys.add.expiry.options.${ opt }`) }));
|
|
73
81
|
|
|
74
|
-
// When the TTL is
|
|
82
|
+
// When the TTL is greater than 0, present only two options
|
|
75
83
|
// (1) The maximum allowed
|
|
76
84
|
// (2) Custom
|
|
77
|
-
if (this.maxTTL
|
|
85
|
+
if (this.maxTTL > 0 ) {
|
|
78
86
|
const now = day();
|
|
79
87
|
const expiry = now.add(this.maxTTL, 'minute');
|
|
80
88
|
const max = diffFrom(expiry, now, this.t);
|
|
81
89
|
|
|
82
90
|
opts = opts.filter((opt) => opt.value === 'custom');
|
|
83
91
|
opts.unshift({ value: 'max', label: this.t('accountAndKeys.apiKeys.add.expiry.options.maximum', { value: max.string }) });
|
|
92
|
+
} else {
|
|
93
|
+
// maxTTL <= 0 means there is no maximum, so we can show the 'never' option which results in an infinite TTL
|
|
94
|
+
// OR if we set a positive TTL, then it assumes that value
|
|
95
|
+
opts = opts.filter((opt) => opt.value === 'never' || opt.value === 'custom');
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
return opts;
|
|
@@ -91,6 +103,9 @@ export default {
|
|
|
91
103
|
|
|
92
104
|
return filtered.map((opt) => ({ value: opt, label: this.t(`accountAndKeys.apiKeys.add.customExpiry.options.${ opt }`) }));
|
|
93
105
|
},
|
|
106
|
+
hasNeverOption() {
|
|
107
|
+
return this.expiryOptions?.filter((opt) => opt.value === 'never')?.length === 1;
|
|
108
|
+
}
|
|
94
109
|
},
|
|
95
110
|
|
|
96
111
|
mounted() {
|
|
@@ -115,31 +130,33 @@ export default {
|
|
|
115
130
|
});
|
|
116
131
|
},
|
|
117
132
|
|
|
118
|
-
async actuallySave(
|
|
133
|
+
async actuallySave() {
|
|
134
|
+
// update expiration value before save
|
|
119
135
|
this.updateExpiry();
|
|
120
|
-
if ( this.isCreate ) {
|
|
121
|
-
// Description is a bit weird, so need to clone and set this
|
|
122
|
-
// rather than use this.value - need to find a way to set this if we ever
|
|
123
|
-
// want to allow edit (which I don't think we do)
|
|
124
|
-
const res = await this.value.save();
|
|
125
136
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
if ( this.isCreate ) {
|
|
138
|
+
const steveToken = await this.$store.dispatch('management/create', {
|
|
139
|
+
type: EXT.TOKEN,
|
|
140
|
+
spec: {
|
|
141
|
+
description: this.form.description,
|
|
142
|
+
kind: '',
|
|
143
|
+
userPrincipal: null, // will be set by the backend to the current user
|
|
144
|
+
clusterName: this.form.clusterName,
|
|
145
|
+
enabled: this.form.enabled,
|
|
146
|
+
ttl: this.ttl
|
|
147
|
+
// userID: not needed as it will be set by the backend to the current user
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const steveTokenSaved = await steveToken.save();
|
|
152
|
+
|
|
153
|
+
this.created = steveTokenSaved;
|
|
154
|
+
this.ttlLimited = this.created?.spec?.ttl !== this.ttl;
|
|
155
|
+
const token = this.created?.status?.bearerToken?.split(':');
|
|
129
156
|
|
|
130
157
|
this.accessKey = token[0];
|
|
131
158
|
this.secretKey = (token.length > 1) ? token[1] : '';
|
|
132
|
-
this.token = this.created
|
|
133
|
-
|
|
134
|
-
// Force a refresh of the token so we get the expiry date correctly
|
|
135
|
-
await this.$store.dispatch('rancher/find', {
|
|
136
|
-
type: NORMAN.TOKEN,
|
|
137
|
-
id: res.id,
|
|
138
|
-
opt: { force: true }
|
|
139
|
-
}, { root: true });
|
|
140
|
-
} else {
|
|
141
|
-
// Note: update of existing key not supported currently
|
|
142
|
-
await this.value.save();
|
|
159
|
+
this.token = this.created?.status?.bearerToken;
|
|
143
160
|
}
|
|
144
161
|
},
|
|
145
162
|
|
|
@@ -159,7 +176,9 @@ export default {
|
|
|
159
176
|
const units = (v === 'custom') ? this.form.customExpiryUnits : v;
|
|
160
177
|
let ttl = 0;
|
|
161
178
|
|
|
162
|
-
if (
|
|
179
|
+
if (v === 'never') {
|
|
180
|
+
ttl = -1;
|
|
181
|
+
} else if (units === 'max') {
|
|
163
182
|
ttl = this.maxTTL * 60 * 1000;
|
|
164
183
|
} else if ( units !== 'never' ) {
|
|
165
184
|
const now = day();
|
|
@@ -167,7 +186,8 @@ export default {
|
|
|
167
186
|
|
|
168
187
|
ttl = expiry.diff(now);
|
|
169
188
|
}
|
|
170
|
-
|
|
189
|
+
|
|
190
|
+
this.ttl = ttl;
|
|
171
191
|
}
|
|
172
192
|
}
|
|
173
193
|
};
|
|
@@ -178,7 +198,7 @@ export default {
|
|
|
178
198
|
<div class="pl-10 pr-10">
|
|
179
199
|
<LabeledInput
|
|
180
200
|
key="description"
|
|
181
|
-
v-model:value="
|
|
201
|
+
v-model:value="form.description"
|
|
182
202
|
:placeholder="t('accountAndKeys.apiKeys.add.description.placeholder')"
|
|
183
203
|
label-key="accountAndKeys.apiKeys.add.description.label"
|
|
184
204
|
mode="edit"
|
|
@@ -186,13 +206,30 @@ export default {
|
|
|
186
206
|
/>
|
|
187
207
|
|
|
188
208
|
<LabeledSelect
|
|
189
|
-
v-model:value="
|
|
209
|
+
v-model:value="form.clusterName"
|
|
190
210
|
class="mt-20 scope-select"
|
|
191
211
|
label-key="accountAndKeys.apiKeys.add.scope"
|
|
192
212
|
:options="scopes"
|
|
193
213
|
/>
|
|
194
214
|
|
|
195
|
-
<
|
|
215
|
+
<Checkbox
|
|
216
|
+
v-model:value="form.enabled"
|
|
217
|
+
class="mt-20 mb-20"
|
|
218
|
+
:mode="mode"
|
|
219
|
+
label-key="accountAndKeys.apiKeys.add.enabled"
|
|
220
|
+
/>
|
|
221
|
+
|
|
222
|
+
<Banner
|
|
223
|
+
v-if="hasNeverOption"
|
|
224
|
+
color="warning"
|
|
225
|
+
class="mt-20"
|
|
226
|
+
>
|
|
227
|
+
<div>
|
|
228
|
+
{{ t('accountAndKeys.apiKeys.info.expiryOptionsWithNever') }}
|
|
229
|
+
</div>
|
|
230
|
+
</Banner>
|
|
231
|
+
|
|
232
|
+
<h5 class="mb-20">
|
|
196
233
|
{{ t('accountAndKeys.apiKeys.add.expiry.label') }}
|
|
197
234
|
</h5>
|
|
198
235
|
|
|
@@ -204,7 +241,9 @@ export default {
|
|
|
204
241
|
class="mr-20"
|
|
205
242
|
name="expiryGroup"
|
|
206
243
|
/>
|
|
207
|
-
<div
|
|
244
|
+
<div
|
|
245
|
+
class="ml-20 mt-10 expiry"
|
|
246
|
+
>
|
|
208
247
|
<input
|
|
209
248
|
v-model="form.customExpiry"
|
|
210
249
|
:disabled="form.expiryType !== 'custom'"
|
|
@@ -68,6 +68,8 @@ describe('component: Workload', () => {
|
|
|
68
68
|
$store: {
|
|
69
69
|
getters: {
|
|
70
70
|
'cluster/schemaFor': jest.fn(),
|
|
71
|
+
'cluster/canList': jest.fn(),
|
|
72
|
+
currentStore: () => 'cluster',
|
|
71
73
|
'type-map/labelFor': jest.fn(),
|
|
72
74
|
'i18n/t': (text: string, v: {[key:string]: string}) => {
|
|
73
75
|
return `${ text }, ${ Object.values(v || {}) }`;
|
|
@@ -99,42 +101,5 @@ describe('component: Workload', () => {
|
|
|
99
101
|
|
|
100
102
|
expect(result).toStrictEqual(newMessage);
|
|
101
103
|
});
|
|
102
|
-
|
|
103
|
-
describe('secondaryResourceDataConfig', () => {
|
|
104
|
-
it('should filter out nodes with control-plane or etcd taints from workerNodes parsingFunc', () => {
|
|
105
|
-
const allNodeObjects = [
|
|
106
|
-
{
|
|
107
|
-
id: 'node-1',
|
|
108
|
-
spec: { taints: [{ key: 'node-role.kubernetes.io/control-plane', effect: 'NoSchedule' }] }
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
id: 'node-2',
|
|
112
|
-
spec: { taints: [{ key: 'node-role.kubernetes.io/etcd', effect: 'NoSchedule' }] }
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
id: 'node-3',
|
|
116
|
-
spec: { taints: [{ key: 'node-role.kubernetes.io/worker', effect: 'NoSchedule' }] }
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
id: 'node-4',
|
|
120
|
-
spec: { taints: [] }
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: 'node-5',
|
|
124
|
-
spec: {}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: 'node-6',
|
|
128
|
-
spec: null
|
|
129
|
-
}
|
|
130
|
-
];
|
|
131
|
-
|
|
132
|
-
const { data } = (Workload.mixins[2] as any).methods.secondaryResourceDataConfig.apply({ value: { metadata: { namespace: 'test' } } });
|
|
133
|
-
const workerNodesParsingFunc = data.node.applyTo.find((r: any) => r.var === 'workerNodes').parsingFunc;
|
|
134
|
-
const result = workerNodesParsingFunc(allNodeObjects);
|
|
135
|
-
|
|
136
|
-
expect(result).toStrictEqual(['node-3', 'node-4', 'node-5', 'node-6']);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
104
|
});
|
|
140
105
|
});
|
package/edit/workload/index.vue
CHANGED
|
@@ -4,6 +4,7 @@ import FormValidation from '@shell/mixins/form-validation';
|
|
|
4
4
|
import WorkLoadMixin from '@shell/edit/workload/mixins/workload';
|
|
5
5
|
import { mapGetters } from 'vuex';
|
|
6
6
|
import { FORM_TYPES } from '@shell/components/form/Security';
|
|
7
|
+
import { NODE } from '@shell/config/types';
|
|
7
8
|
|
|
8
9
|
export default {
|
|
9
10
|
name: 'Workload',
|
|
@@ -21,9 +22,13 @@ export default {
|
|
|
21
22
|
},
|
|
22
23
|
},
|
|
23
24
|
data() {
|
|
25
|
+
const inStore = this.$store.getters['currentStore'](NODE);
|
|
26
|
+
const canNode = this.$store.getters[`${ inStore }/canList`](NODE);
|
|
27
|
+
|
|
24
28
|
return {
|
|
25
29
|
selectedName: null,
|
|
26
30
|
errors: [],
|
|
31
|
+
canNode,
|
|
27
32
|
FORM_TYPES
|
|
28
33
|
};
|
|
29
34
|
},
|
|
@@ -506,11 +511,11 @@ export default {
|
|
|
506
511
|
<PodAffinity
|
|
507
512
|
:mode="mode"
|
|
508
513
|
:value="podTemplateSpec"
|
|
509
|
-
:nodes="allNodeObjects"
|
|
510
514
|
:loading="isLoadingSecondaryResources"
|
|
511
515
|
/>
|
|
512
516
|
</Tab>
|
|
513
517
|
<Tab
|
|
518
|
+
v-if="canNode"
|
|
514
519
|
:label="t('workload.container.titles.nodeScheduling')"
|
|
515
520
|
name="nodeScheduling-pod"
|
|
516
521
|
:weight="tabWeightMap['nodeScheduling']"
|
|
@@ -518,7 +523,6 @@ export default {
|
|
|
518
523
|
<NodeScheduling
|
|
519
524
|
:mode="mode"
|
|
520
525
|
:value="podTemplateSpec"
|
|
521
|
-
:nodes="workerNodes"
|
|
522
526
|
:loading="isLoadingSecondaryResources"
|
|
523
527
|
/>
|
|
524
528
|
</Tab>
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
CONFIG_MAP,
|
|
6
6
|
SECRET,
|
|
7
7
|
WORKLOAD_TYPES,
|
|
8
|
-
NODE,
|
|
9
8
|
SERVICE,
|
|
10
9
|
PVC,
|
|
11
10
|
SERVICE_ACCOUNT,
|
|
@@ -263,9 +262,6 @@ export default {
|
|
|
263
262
|
return {
|
|
264
263
|
secondaryResourceData: this.secondaryResourceDataConfig(),
|
|
265
264
|
namespacedConfigMaps: [],
|
|
266
|
-
allNodes: null,
|
|
267
|
-
workerNodes: null,
|
|
268
|
-
allNodeObjects: [],
|
|
269
265
|
namespacedSecrets: [],
|
|
270
266
|
imagePullNamespacedSecrets: [],
|
|
271
267
|
allServices: [],
|
|
@@ -690,34 +686,6 @@ export default {
|
|
|
690
686
|
}
|
|
691
687
|
]
|
|
692
688
|
},
|
|
693
|
-
[NODE]: {
|
|
694
|
-
applyTo: [
|
|
695
|
-
{ var: 'allNodeObjects' },
|
|
696
|
-
{
|
|
697
|
-
var: 'allNodes',
|
|
698
|
-
parsingFunc: (data) => {
|
|
699
|
-
return data.map((node) => node.id);
|
|
700
|
-
}
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
var: 'workerNodes',
|
|
704
|
-
parsingFunc: (data) => {
|
|
705
|
-
const keys = [
|
|
706
|
-
`node-role.kubernetes.io/control-plane`,
|
|
707
|
-
`node-role.kubernetes.io/etcd`
|
|
708
|
-
];
|
|
709
|
-
|
|
710
|
-
return data
|
|
711
|
-
.filter((node) => {
|
|
712
|
-
const taints = node?.spec?.taints || [];
|
|
713
|
-
|
|
714
|
-
return taints.every((taint) => !keys.includes(taint.key));
|
|
715
|
-
})
|
|
716
|
-
.map((node) => node.id);
|
|
717
|
-
}
|
|
718
|
-
},
|
|
719
|
-
]
|
|
720
|
-
},
|
|
721
689
|
[SERVICE]: {
|
|
722
690
|
applyTo: [
|
|
723
691
|
{ var: 'allServices' },
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
import ManagementSetting from '@shell/list/management.cattle.io.setting.vue';
|
|
4
|
+
import { SETTING } from '@shell/config/settings';
|
|
5
|
+
|
|
6
|
+
const mockStore = {
|
|
7
|
+
getters: {
|
|
8
|
+
'prefs/get': () => false,
|
|
9
|
+
'i18n/t': (key: string) => key,
|
|
10
|
+
},
|
|
11
|
+
dispatch: jest.fn()
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const mockRoute = {
|
|
15
|
+
hash: '',
|
|
16
|
+
params: {}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const mockRouter = {
|
|
20
|
+
push: jest.fn(),
|
|
21
|
+
replace: jest.fn()
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Mock scrollIntoView
|
|
25
|
+
const mockScrollIntoView = jest.fn();
|
|
26
|
+
|
|
27
|
+
Object.defineProperty(Element.prototype, 'scrollIntoView', {
|
|
28
|
+
value: mockScrollIntoView,
|
|
29
|
+
writable: true
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Mock querySelector
|
|
33
|
+
const mockQuerySelector = jest.fn();
|
|
34
|
+
|
|
35
|
+
Object.defineProperty(document, 'querySelector', {
|
|
36
|
+
value: mockQuerySelector,
|
|
37
|
+
writable: true
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('managementSetting - Scroll Behavior', () => {
|
|
41
|
+
let wrapper: any;
|
|
42
|
+
|
|
43
|
+
const createWrapper = (routeHash = '') => {
|
|
44
|
+
const route = { ...mockRoute, hash: routeHash };
|
|
45
|
+
|
|
46
|
+
return mount(ManagementSetting, {
|
|
47
|
+
global: {
|
|
48
|
+
mocks: {
|
|
49
|
+
$store: mockStore,
|
|
50
|
+
$route: route,
|
|
51
|
+
$router: mockRouter,
|
|
52
|
+
$fetchState: { pending: false }
|
|
53
|
+
},
|
|
54
|
+
stubs: {
|
|
55
|
+
Loading: true,
|
|
56
|
+
Banner: true,
|
|
57
|
+
Setting: true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
jest.clearAllMocks();
|
|
65
|
+
mockScrollIntoView.mockClear();
|
|
66
|
+
mockQuerySelector.mockClear();
|
|
67
|
+
|
|
68
|
+
// Mock successful API response with cluster agent setting
|
|
69
|
+
mockStore.dispatch.mockResolvedValue([
|
|
70
|
+
{
|
|
71
|
+
id: SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS,
|
|
72
|
+
value: '{"enabled": true}',
|
|
73
|
+
default: '{"enabled": false}',
|
|
74
|
+
availableActions: ['edit']
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS,
|
|
78
|
+
value: '{"enabled": true}',
|
|
79
|
+
default: '{"enabled": false}',
|
|
80
|
+
availableActions: ['edit']
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
if (wrapper) {
|
|
87
|
+
wrapper.unmount();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should scroll to hash anchor after data is loaded', async() => {
|
|
92
|
+
const mockElement = document.createElement('div');
|
|
93
|
+
|
|
94
|
+
mockQuerySelector.mockReturnValue(mockElement);
|
|
95
|
+
|
|
96
|
+
wrapper = createWrapper('#cluster-agent-default-priority-class');
|
|
97
|
+
|
|
98
|
+
// Trigger fetch manually since we can't easily test the async fetch hook
|
|
99
|
+
await wrapper.vm.$options.fetch.call(wrapper.vm);
|
|
100
|
+
await nextTick();
|
|
101
|
+
|
|
102
|
+
expect(mockQuerySelector).toHaveBeenCalledWith('#cluster-agent-default-priority-class');
|
|
103
|
+
expect(mockScrollIntoView).toHaveBeenCalledWith({
|
|
104
|
+
behavior: 'smooth',
|
|
105
|
+
block: 'start'
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should not scroll if hash is empty', async() => {
|
|
110
|
+
wrapper = createWrapper('');
|
|
111
|
+
|
|
112
|
+
await wrapper.vm.$options.fetch.call(wrapper.vm);
|
|
113
|
+
await nextTick();
|
|
114
|
+
|
|
115
|
+
expect(mockQuerySelector).not.toHaveBeenCalled();
|
|
116
|
+
expect(mockScrollIntoView).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should not scroll if element is not found', async() => {
|
|
120
|
+
mockQuerySelector.mockReturnValue(null);
|
|
121
|
+
|
|
122
|
+
wrapper = createWrapper('#non-existent-element');
|
|
123
|
+
|
|
124
|
+
await wrapper.vm.$options.fetch.call(wrapper.vm);
|
|
125
|
+
await nextTick();
|
|
126
|
+
|
|
127
|
+
expect(mockQuerySelector).toHaveBeenCalledWith('#non-existent-element');
|
|
128
|
+
expect(mockScrollIntoView).not.toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should scroll to fleet agent setting hash', async() => {
|
|
132
|
+
const mockElement = document.createElement('div');
|
|
133
|
+
|
|
134
|
+
mockQuerySelector.mockReturnValue(mockElement);
|
|
135
|
+
|
|
136
|
+
wrapper = createWrapper('#fleet-agent-default-priority-class');
|
|
137
|
+
|
|
138
|
+
await wrapper.vm.$options.fetch.call(wrapper.vm);
|
|
139
|
+
await nextTick();
|
|
140
|
+
|
|
141
|
+
expect(mockQuerySelector).toHaveBeenCalledWith('#fleet-agent-default-priority-class');
|
|
142
|
+
expect(mockScrollIntoView).toHaveBeenCalledWith({
|
|
143
|
+
behavior: 'smooth',
|
|
144
|
+
block: 'start'
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should handle hash with special characters', async() => {
|
|
149
|
+
const mockElement = document.createElement('div');
|
|
150
|
+
|
|
151
|
+
mockQuerySelector.mockReturnValue(mockElement);
|
|
152
|
+
|
|
153
|
+
wrapper = createWrapper('#setting-with-special-chars_123');
|
|
154
|
+
|
|
155
|
+
await wrapper.vm.$options.fetch.call(wrapper.vm);
|
|
156
|
+
await nextTick();
|
|
157
|
+
|
|
158
|
+
expect(mockQuerySelector).toHaveBeenCalledWith('#setting-with-special-chars_123');
|
|
159
|
+
expect(mockScrollIntoView).toHaveBeenCalledWith({
|
|
160
|
+
behavior: 'smooth',
|
|
161
|
+
block: 'start'
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should render settings with correct IDs for anchors', async() => {
|
|
166
|
+
wrapper = createWrapper();
|
|
167
|
+
|
|
168
|
+
// Manually set the data to simulate successful fetch
|
|
169
|
+
wrapper.vm.settings = [
|
|
170
|
+
{
|
|
171
|
+
id: SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS,
|
|
172
|
+
description: 'test-description',
|
|
173
|
+
data: { value: 'test' },
|
|
174
|
+
customized: false,
|
|
175
|
+
fromEnv: false,
|
|
176
|
+
hasActions: true
|
|
177
|
+
}
|
|
178
|
+
];
|
|
179
|
+
wrapper.vm.provisioningSettings = [
|
|
180
|
+
{
|
|
181
|
+
id: SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS,
|
|
182
|
+
description: 'test-description',
|
|
183
|
+
data: { value: 'test' },
|
|
184
|
+
customized: false,
|
|
185
|
+
fromEnv: false,
|
|
186
|
+
hasActions: true
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
await nextTick();
|
|
191
|
+
|
|
192
|
+
const settingDivs = wrapper.findAll('div[id]');
|
|
193
|
+
const ids = settingDivs.map((div: any) => div.attributes('id'));
|
|
194
|
+
|
|
195
|
+
expect(ids).toContain(SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS);
|
|
196
|
+
expect(ids).toContain(SETTING.FLEET_AGENT_DEFAULT_PRIORITY_CLASS);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -66,6 +66,17 @@ export default {
|
|
|
66
66
|
|
|
67
67
|
this.settings = settings;
|
|
68
68
|
this.provisioningSettings = provisioningSettings;
|
|
69
|
+
|
|
70
|
+
this.$nextTick(() => {
|
|
71
|
+
// Handle scrolling to hash anchor after data is loaded
|
|
72
|
+
if (this.$route.hash) {
|
|
73
|
+
const element = document.querySelector(this.$route.hash);
|
|
74
|
+
|
|
75
|
+
if (element) {
|
|
76
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
69
80
|
},
|
|
70
81
|
|
|
71
82
|
data() {
|
|
@@ -89,6 +100,7 @@ export default {
|
|
|
89
100
|
</Banner>
|
|
90
101
|
<div
|
|
91
102
|
v-for="(setting) in settings"
|
|
103
|
+
:id="setting.id"
|
|
92
104
|
:key="setting.id"
|
|
93
105
|
>
|
|
94
106
|
<Setting
|
|
@@ -101,6 +113,7 @@ export default {
|
|
|
101
113
|
</h2>
|
|
102
114
|
<div
|
|
103
115
|
v-for="(setting) in provisioningSettings"
|
|
116
|
+
:id="setting.id"
|
|
104
117
|
:key="setting.id"
|
|
105
118
|
>
|
|
106
119
|
<Setting
|
|
@@ -167,6 +167,12 @@ export default {
|
|
|
167
167
|
|
|
168
168
|
},
|
|
169
169
|
|
|
170
|
+
methods: {
|
|
171
|
+
getCustomDetailLink(cluster) {
|
|
172
|
+
return cluster.isCapiHybrid ? null : cluster.detailLocation;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
170
176
|
$loadingResources() {
|
|
171
177
|
// results are filtered so we wouldn't get the correct count on indicator...
|
|
172
178
|
return { loadIndeterminate: true };
|
|
@@ -211,7 +217,6 @@ export default {
|
|
|
211
217
|
</router-link>
|
|
212
218
|
</template>
|
|
213
219
|
</Masthead>
|
|
214
|
-
|
|
215
220
|
<ResourceTable
|
|
216
221
|
:headers="headers"
|
|
217
222
|
:table-actions="true"
|
|
@@ -221,6 +226,8 @@ export default {
|
|
|
221
226
|
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
222
227
|
:data-testid="'cluster-list'"
|
|
223
228
|
:force-update-live-and-delayed="forceUpdateLiveAndDelayed"
|
|
229
|
+
:get-custom-detail-link="getCustomDetailLink"
|
|
230
|
+
:sub-rows="true"
|
|
224
231
|
>
|
|
225
232
|
<!-- Why are state column and subrow overwritten here? -->
|
|
226
233
|
<!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
|
|
@@ -266,6 +273,48 @@ export default {
|
|
|
266
273
|
{{ t('cluster.explore') }}
|
|
267
274
|
</button>
|
|
268
275
|
</template>
|
|
276
|
+
<template #additional-sub-row="{row, fullColspan, tableActions}">
|
|
277
|
+
<tr
|
|
278
|
+
class="capi-unsupported"
|
|
279
|
+
:class="{'has-description': !!row.stateDescription}"
|
|
280
|
+
>
|
|
281
|
+
<td
|
|
282
|
+
v-if="row.isCapiHybrid"
|
|
283
|
+
class="row-check"
|
|
284
|
+
/>
|
|
285
|
+
<td
|
|
286
|
+
v-if="row.isCapiHybrid"
|
|
287
|
+
:data-testid="`capi-unsupported-warning-${row?.metadata?.name}`"
|
|
288
|
+
:colspan="fullColspan - (tableActions ? 1: 0)"
|
|
289
|
+
>
|
|
290
|
+
<div
|
|
291
|
+
class="text-warning"
|
|
292
|
+
:class="{'mt-5': !!row.stateDescription.trim()}"
|
|
293
|
+
>
|
|
294
|
+
<i class="icon icon-warning" />{{ t('cluster.capi.notSupported') }}
|
|
295
|
+
</div>
|
|
296
|
+
</td>
|
|
297
|
+
</tr>
|
|
298
|
+
</template>
|
|
269
299
|
</ResourceTable>
|
|
270
300
|
</div>
|
|
271
301
|
</template>
|
|
302
|
+
|
|
303
|
+
<style scoped lang="scss">
|
|
304
|
+
.capi-unsupported {
|
|
305
|
+
&.has-description {
|
|
306
|
+
border-bottom: none;
|
|
307
|
+
padding: 0px;
|
|
308
|
+
td {
|
|
309
|
+
padding-top: 0px;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
& div {
|
|
314
|
+
& i {
|
|
315
|
+
margin-right: 0.1em;
|
|
316
|
+
}
|
|
317
|
+
display: flex;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
</style>
|
package/list/secret.vue
CHANGED
|
@@ -7,8 +7,8 @@ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable';
|
|
|
7
7
|
import { TableColumn } from '@shell/types/store/type-map';
|
|
8
8
|
import ResourceFetch from '@shell/mixins/resource-fetch';
|
|
9
9
|
import { mapGetters } from 'vuex';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { SECRET_ORIGIN } from '@shell/config/table-headers';
|
|
11
|
+
import { STEVE_SECRET_ORIGIN } from '@shell/config/pagination-table-headers';
|
|
12
12
|
|
|
13
13
|
export default {
|
|
14
14
|
name: 'ListSecret',
|
|
@@ -61,13 +61,8 @@ export default {
|
|
|
61
61
|
|
|
62
62
|
if (this.canViewProjects) {
|
|
63
63
|
// if the user can see projects, add a column to let them know if it's a secret from a project scoped secret
|
|
64
|
-
headers.push(
|
|
65
|
-
headersSSP.push(
|
|
66
|
-
if (this.currentCluster.isLocal) {
|
|
67
|
-
// if the user is on the local cluster, add a column to let them know if it's a project scoped secret (from another cluster)
|
|
68
|
-
headers.push(SECRET_PROJECT_SCOPED);
|
|
69
|
-
headersSSP.push(SECRET_PROJECT_SCOPED);
|
|
70
|
-
}
|
|
64
|
+
headers.push(SECRET_ORIGIN);
|
|
65
|
+
headersSSP.push(STEVE_SECRET_ORIGIN);
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
headers.push(this.namespacedHeaders[this.namespacedHeaders.length - 1]);
|