@rancher/shell 0.3.11 → 0.3.12
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/translations/en-us.yaml +51 -5
- package/chart/monitoring/StorageClassSelector.vue +1 -0
- package/chart/monitoring/index.vue +4 -0
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/ActionMenu.vue +1 -1
- package/components/DetailTop.vue +0 -2
- package/components/ExplorerMembers.vue +22 -10
- package/components/ExplorerProjectsNamespaces.vue +1 -0
- package/components/GrafanaDashboard.vue +2 -2
- package/components/Inactivity.vue +1 -0
- package/components/ModalWithCard.vue +1 -0
- package/components/Tabbed/index.vue +2 -0
- package/components/Wizard.vue +4 -3
- package/components/form/KeyValue.vue +12 -7
- package/components/form/NodeAffinity.vue +29 -7
- package/components/form/PodAffinity.vue +27 -7
- package/components/form/Taints.vue +6 -0
- package/components/formatter/ExtensionCache.vue +74 -0
- package/components/nav/Header.vue +1 -0
- package/components/nav/WindowManager/ContainerShell.vue +10 -0
- package/components/nav/WindowManager/index.vue +1 -0
- package/config/product/explorer.js +1 -10
- package/config/product/monitoring.js +2 -1
- package/config/router.js +3 -3
- package/config/table-headers.js +32 -24
- package/config/uiplugins.js +11 -0
- package/config/workload.ts +1 -0
- package/core/types.ts +25 -7
- package/creators/pkg/files/.github/workflows/build-container.yml +64 -0
- package/creators/pkg/init +13 -6
- package/detail/node.vue +2 -2
- package/detail/workload/index.vue +1 -1
- package/edit/__tests__/management.cattle.io.setting.test.ts +1 -1
- package/edit/autoscaling.horizontalpodautoscaler/metric-target.vue +0 -2
- package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +43 -0
- package/edit/logging.banzaicloud.io.output/index.vue +8 -5
- package/edit/logging.banzaicloud.io.output/providers/__tests__/loki.test.ts +13 -0
- package/edit/logging.banzaicloud.io.output/providers/loki.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +0 -2
- package/edit/monitoring.coreos.com.receiver/index.vue +32 -1
- package/edit/monitoring.coreos.com.receiver/types/email.vue +12 -4
- package/edit/namespace.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +36 -6
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +2 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +58 -13
- package/middleware/authenticated.js +1 -0
- package/models/__tests__/batch.cronjob.test.ts +88 -0
- package/models/cluster/node.js +8 -0
- package/models/management.cattle.io.clusterroletemplatebinding.js +5 -1
- package/models/projectroletemplatebinding.js +9 -1
- package/models/workload.js +1 -1
- package/package.json +1 -1
- package/pages/__tests__/prefs.test.ts +96 -0
- package/pages/auth/setup.vue +13 -13
- package/pages/c/_cluster/apps/charts/chart.vue +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +5 -2
- package/pages/c/_cluster/monitoring/index.vue +10 -5
- package/pages/c/_cluster/settings/performance.vue +2 -0
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +601 -0
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +183 -0
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +50 -9
- package/pages/c/_cluster/uiplugins/index.vue +329 -224
- package/pages/fail-whale.vue +1 -1
- package/pages/home.vue +11 -0
- package/pages/prefs.vue +20 -1
- package/plugins/plugin.js +1 -1
- package/public/index.html +6 -1
- package/rancher-components/components/Card/Card.vue +1 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +1 -0
- package/scripts/extension/bundle +20 -4
- package/scripts/extension/helm/charts/ui-plugin-server/.helmignore +23 -0
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +20 -0
- package/scripts/extension/helm/charts/ui-plugin-server/templates/_helpers.tpl +52 -0
- package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +12 -0
- package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +6 -0
- package/scripts/extension/helm/package/Dockerfile +27 -0
- package/scripts/extension/helm/package/nginx.conf +17 -0
- package/scripts/extension/helm/scripts/package +23 -0
- package/scripts/extension/helm/scripts/patch +101 -0
- package/scripts/extension/helm/scripts/version +31 -0
- package/scripts/extension/helmpatch +3 -25
- package/scripts/extension/publish +47 -32
- package/types/shell/index.d.ts +30 -24
- package/utils/__tests__/grafana.test.ts +2 -2
- package/utils/error.js +11 -0
- package/utils/grafana.js +5 -4
- package/vue.config.js +3 -17
|
@@ -59,6 +59,12 @@ export default {
|
|
|
59
59
|
type: Array,
|
|
60
60
|
default: () => []
|
|
61
61
|
},
|
|
62
|
+
|
|
63
|
+
// Is the UI busy (e.g. during save)
|
|
64
|
+
busy: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false,
|
|
67
|
+
}
|
|
62
68
|
},
|
|
63
69
|
|
|
64
70
|
data() {
|
|
@@ -117,8 +123,7 @@ export default {
|
|
|
117
123
|
|
|
118
124
|
isWindows() {
|
|
119
125
|
return this.value?.config?.os === 'windows';
|
|
120
|
-
}
|
|
121
|
-
|
|
126
|
+
},
|
|
122
127
|
},
|
|
123
128
|
|
|
124
129
|
watch: {
|
|
@@ -133,6 +138,18 @@ export default {
|
|
|
133
138
|
}
|
|
134
139
|
},
|
|
135
140
|
|
|
141
|
+
/**
|
|
142
|
+
* On creation, ensure that the pool is marked valid - custom machine pools can emit further validation events
|
|
143
|
+
*/
|
|
144
|
+
created() {
|
|
145
|
+
this.$emit('validationChanged', true);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
beforeDestroy() {
|
|
149
|
+
// Ensure we emit validation event so parent can forget any validation for this Machine Pool when it is removed
|
|
150
|
+
this.$emit('validationChanged', undefined);
|
|
151
|
+
},
|
|
152
|
+
|
|
136
153
|
methods: {
|
|
137
154
|
async test() {
|
|
138
155
|
if ( typeof this.$refs.configComponent?.test === 'function' ) {
|
|
@@ -166,6 +183,11 @@ export default {
|
|
|
166
183
|
if (advancedComponent && !advancedComponent.show) {
|
|
167
184
|
advancedComponent.toggle();
|
|
168
185
|
}
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
// Propagate up validation status for this Machine Pool
|
|
189
|
+
validationChanged(val) {
|
|
190
|
+
this.$emit('validationChanged', val);
|
|
169
191
|
}
|
|
170
192
|
}
|
|
171
193
|
};
|
|
@@ -188,7 +210,7 @@ export default {
|
|
|
188
210
|
:mode="mode"
|
|
189
211
|
:label="t('cluster.machinePool.name.label')"
|
|
190
212
|
:required="true"
|
|
191
|
-
:disabled="!value.config || !!value.config.id"
|
|
213
|
+
:disabled="!value.config || !!value.config.id || busy"
|
|
192
214
|
/>
|
|
193
215
|
</div>
|
|
194
216
|
<div class="col span-4">
|
|
@@ -196,6 +218,7 @@ export default {
|
|
|
196
218
|
v-model.number="value.pool.quantity"
|
|
197
219
|
:mode="mode"
|
|
198
220
|
:label="t('cluster.machinePool.quantity.label')"
|
|
221
|
+
:disabled="busy"
|
|
199
222
|
type="number"
|
|
200
223
|
min="0"
|
|
201
224
|
:required="true"
|
|
@@ -209,18 +232,19 @@ export default {
|
|
|
209
232
|
v-model="value.pool.etcdRole"
|
|
210
233
|
:mode="mode"
|
|
211
234
|
:label="t('cluster.machinePool.role.etcd')"
|
|
212
|
-
:disabled="isWindows"
|
|
235
|
+
:disabled="isWindows || busy"
|
|
213
236
|
/>
|
|
214
237
|
<Checkbox
|
|
215
238
|
v-model="value.pool.controlPlaneRole"
|
|
216
239
|
:mode="mode"
|
|
217
240
|
:label="t('cluster.machinePool.role.controlPlane')"
|
|
218
|
-
:disabled="isWindows"
|
|
241
|
+
:disabled="isWindows || busy"
|
|
219
242
|
/>
|
|
220
243
|
<Checkbox
|
|
221
244
|
v-model="value.pool.workerRole"
|
|
222
245
|
:mode="mode"
|
|
223
246
|
:label="t('cluster.machinePool.role.worker')"
|
|
247
|
+
:disabled="busy"
|
|
224
248
|
/>
|
|
225
249
|
</div>
|
|
226
250
|
</div>
|
|
@@ -237,9 +261,11 @@ export default {
|
|
|
237
261
|
:credential-id="credentialId"
|
|
238
262
|
:pool-index="idx"
|
|
239
263
|
:machine-pools="machinePools"
|
|
264
|
+
:busy="busy"
|
|
240
265
|
@error="e=>errors = e"
|
|
241
266
|
@updateMachineCount="updateMachineCount"
|
|
242
267
|
@expandAdvanced="expandAdvanced"
|
|
268
|
+
@validationChanged="validationChanged"
|
|
243
269
|
/>
|
|
244
270
|
<Banner
|
|
245
271
|
v-else-if="value.configMissing"
|
|
@@ -279,6 +305,7 @@ export default {
|
|
|
279
305
|
:mode="mode"
|
|
280
306
|
:output-modifier="true"
|
|
281
307
|
:base-unit="t('cluster.machinePool.autoReplace.unit')"
|
|
308
|
+
:disabled="busy"
|
|
282
309
|
@input="value.pool.unhealthyNodeTimeout = `${unhealthyNodeTimeoutInteger}s`"
|
|
283
310
|
/>
|
|
284
311
|
</div>
|
|
@@ -290,6 +317,7 @@ export default {
|
|
|
290
317
|
v-model="value.pool.drainBeforeDelete"
|
|
291
318
|
:mode="mode"
|
|
292
319
|
:label="t('cluster.machinePool.drain.label')"
|
|
320
|
+
:disabled="busy"
|
|
293
321
|
/>
|
|
294
322
|
</div>
|
|
295
323
|
</div>
|
|
@@ -297,16 +325,18 @@ export default {
|
|
|
297
325
|
<KeyValue
|
|
298
326
|
v-model="value.pool.labels"
|
|
299
327
|
:add-label="t('labels.addLabel')"
|
|
300
|
-
:
|
|
328
|
+
:disabled="busy"
|
|
301
329
|
:title="t('cluster.machinePool.labels.label')"
|
|
302
330
|
:read-allowed="false"
|
|
303
331
|
:value-can-be-empty="true"
|
|
304
332
|
/>
|
|
333
|
+
|
|
305
334
|
<div class="spacer" />
|
|
306
335
|
|
|
307
336
|
<Taints
|
|
308
337
|
v-model="value.pool.taints"
|
|
309
338
|
:mode="mode"
|
|
339
|
+
:disabled="busy"
|
|
310
340
|
/>
|
|
311
341
|
|
|
312
342
|
<portal-target
|
|
@@ -240,7 +240,7 @@ describe('component: rke2', () => {
|
|
|
240
240
|
stubs: defaultStubs
|
|
241
241
|
});
|
|
242
242
|
|
|
243
|
-
expect(wrapper.vm.validationPassed
|
|
243
|
+
expect((wrapper.vm as any).validationPassed).toBe(result);
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
it('should allow creation of K3 clusters if pool machines are missing', () => {
|
|
@@ -265,6 +265,6 @@ describe('component: rke2', () => {
|
|
|
265
265
|
stubs: defaultStubs
|
|
266
266
|
});
|
|
267
267
|
|
|
268
|
-
expect(wrapper.vm.validationPassed
|
|
268
|
+
expect((wrapper.vm as any).validationPassed).toBe(true);
|
|
269
269
|
});
|
|
270
270
|
});
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from '@shell/utils/object';
|
|
27
27
|
import { allHash } from '@shell/utils/promise';
|
|
28
28
|
import { sortBy } from '@shell/utils/sort';
|
|
29
|
+
|
|
29
30
|
import { camelToTitle, nlToBr } from '@shell/utils/string';
|
|
30
31
|
import { compare, sortable } from '@shell/utils/version';
|
|
31
32
|
import { isHarvesterSatisfiesVersion } from '@shell/utils/cluster';
|
|
@@ -330,7 +331,7 @@ export default {
|
|
|
330
331
|
allPSPs: null,
|
|
331
332
|
allPSAs: [],
|
|
332
333
|
nodeComponent: null,
|
|
333
|
-
credentialId:
|
|
334
|
+
credentialId: '',
|
|
334
335
|
credential: null,
|
|
335
336
|
machinePools: null,
|
|
336
337
|
rke2Versions: null,
|
|
@@ -361,6 +362,8 @@ export default {
|
|
|
361
362
|
psps: null, // List of policies if any
|
|
362
363
|
truncateHostnames: truncateLimit === NETBIOS_TRUNCATION_LENGTH,
|
|
363
364
|
truncateLimit,
|
|
365
|
+
busy: false,
|
|
366
|
+
machinePoolValidation: {} // map of validation states for each machine pool
|
|
364
367
|
};
|
|
365
368
|
},
|
|
366
369
|
|
|
@@ -706,9 +709,20 @@ export default {
|
|
|
706
709
|
return false;
|
|
707
710
|
}
|
|
708
711
|
|
|
712
|
+
if (this.customCredentialComponentRequired === false) {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
|
|
709
716
|
return true;
|
|
710
717
|
},
|
|
711
718
|
|
|
719
|
+
/**
|
|
720
|
+
* Only for extensions - extension can register a 'false' cloud credential to indicate that a cloud credential is not needed
|
|
721
|
+
*/
|
|
722
|
+
customCredentialComponentRequired() {
|
|
723
|
+
return this.$plugin.getDynamic('cloud-credential', this.provider);
|
|
724
|
+
},
|
|
725
|
+
|
|
712
726
|
hasMachinePools() {
|
|
713
727
|
if ( this.provider === 'custom' || this.provider === 'import' ) {
|
|
714
728
|
return false;
|
|
@@ -1016,6 +1030,17 @@ export default {
|
|
|
1016
1030
|
return false;
|
|
1017
1031
|
}
|
|
1018
1032
|
},
|
|
1033
|
+
|
|
1034
|
+
validationPassed() {
|
|
1035
|
+
const validRequiredPools = this.hasMachinePools ? this.hasRequiredNodes() : true;
|
|
1036
|
+
|
|
1037
|
+
let base = (this.provider === 'custom' || this.isElementalCluster || !!this.credentialId);
|
|
1038
|
+
|
|
1039
|
+
// and in all of the validation statuses for each machine pool
|
|
1040
|
+
Object.values(this.machinePoolValidation).forEach(v => (base = base && v));
|
|
1041
|
+
|
|
1042
|
+
return validRequiredPools && base;
|
|
1043
|
+
},
|
|
1019
1044
|
},
|
|
1020
1045
|
|
|
1021
1046
|
watch: {
|
|
@@ -1260,11 +1285,10 @@ export default {
|
|
|
1260
1285
|
|
|
1261
1286
|
async syncMachineConfigWithLatest(machinePool) {
|
|
1262
1287
|
if (machinePool?.config?.id) {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
});
|
|
1288
|
+
// Use management/request instead of management/find to avoid overwriting the current machine pool in the store
|
|
1289
|
+
const _latestConfig = await this.$store.dispatch('management/request', { url: `/v1/${ machinePool.config.type }s/${ machinePool.config.id }` });
|
|
1290
|
+
const latestConfig = await this.$store.dispatch('management/create', _latestConfig);
|
|
1291
|
+
|
|
1268
1292
|
const clonedCurrentConfig = await this.$store.dispatch('management/clone', { resource: machinePool.config });
|
|
1269
1293
|
const clonedLatestConfig = await this.$store.dispatch('management/clone', { resource: latestConfig });
|
|
1270
1294
|
|
|
@@ -1284,6 +1308,7 @@ export default {
|
|
|
1284
1308
|
}
|
|
1285
1309
|
|
|
1286
1310
|
await this.syncMachineConfigWithLatest(entry);
|
|
1311
|
+
|
|
1287
1312
|
// Capitals and such aren't allowed;
|
|
1288
1313
|
set(entry.pool, 'name', normalizeName(entry.pool.name) || 'pool');
|
|
1289
1314
|
|
|
@@ -1340,12 +1365,6 @@ export default {
|
|
|
1340
1365
|
return this.nodeTotals?.color && Object.values(this.nodeTotals.color).every(color => color !== NODE_TOTAL.error.color);
|
|
1341
1366
|
},
|
|
1342
1367
|
|
|
1343
|
-
validationPassed() {
|
|
1344
|
-
const validMachinePools = this.hasMachinePools ? this.hasRequiredNodes() : true;
|
|
1345
|
-
|
|
1346
|
-
return (this.provider === 'custom' || this.isElementalCluster || !!this.credentialId) && validMachinePools;
|
|
1347
|
-
},
|
|
1348
|
-
|
|
1349
1368
|
cancelCredential() {
|
|
1350
1369
|
if ( this.$refs.cruresource ) {
|
|
1351
1370
|
this.$refs.cruresource.emitOrRoute();
|
|
@@ -1398,7 +1417,18 @@ export default {
|
|
|
1398
1417
|
});
|
|
1399
1418
|
},
|
|
1400
1419
|
|
|
1420
|
+
// Set busy before save and clear after save
|
|
1401
1421
|
async saveOverride(btnCb) {
|
|
1422
|
+
this.$set(this, 'busy', true);
|
|
1423
|
+
|
|
1424
|
+
return await this._doSaveOverride((done) => {
|
|
1425
|
+
this.$set(this, 'busy', false);
|
|
1426
|
+
|
|
1427
|
+
return btnCb(done);
|
|
1428
|
+
});
|
|
1429
|
+
},
|
|
1430
|
+
|
|
1431
|
+
async _doSaveOverride(btnCb) {
|
|
1402
1432
|
if ( this.errors ) {
|
|
1403
1433
|
clear(this.errors);
|
|
1404
1434
|
}
|
|
@@ -2048,6 +2078,16 @@ export default {
|
|
|
2048
2078
|
this.lastDefaultPodSecurityPolicyTemplateName = value;
|
|
2049
2079
|
},
|
|
2050
2080
|
|
|
2081
|
+
/**
|
|
2082
|
+
* Track Machine Pool validation status
|
|
2083
|
+
*/
|
|
2084
|
+
machinePoolValidationChanged(id, value) {
|
|
2085
|
+
if (value === undefined) {
|
|
2086
|
+
this.$delete(this.machinePoolValidation, id);
|
|
2087
|
+
} else {
|
|
2088
|
+
this.$set(this.machinePoolValidation, id, value);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2051
2091
|
},
|
|
2052
2092
|
};
|
|
2053
2093
|
</script>
|
|
@@ -2063,7 +2103,7 @@ export default {
|
|
|
2063
2103
|
v-else
|
|
2064
2104
|
ref="cruresource"
|
|
2065
2105
|
:mode="mode"
|
|
2066
|
-
:validation-passed="validationPassed
|
|
2106
|
+
:validation-passed="validationPassed && fvFormIsValid"
|
|
2067
2107
|
:resource="value"
|
|
2068
2108
|
:errors="errors"
|
|
2069
2109
|
:cancel-event="true"
|
|
@@ -2092,6 +2132,7 @@ export default {
|
|
|
2092
2132
|
:provider="provider"
|
|
2093
2133
|
:cancel="cancelCredential"
|
|
2094
2134
|
:showing-form="showForm"
|
|
2135
|
+
class="mt-20"
|
|
2095
2136
|
/>
|
|
2096
2137
|
|
|
2097
2138
|
<div
|
|
@@ -2166,6 +2207,7 @@ export default {
|
|
|
2166
2207
|
:name="obj.id"
|
|
2167
2208
|
:label="obj.pool.name || '(Not Named)'"
|
|
2168
2209
|
:show-header="false"
|
|
2210
|
+
:error="!machinePoolValidation[obj.id]"
|
|
2169
2211
|
>
|
|
2170
2212
|
<MachinePool
|
|
2171
2213
|
ref="pool"
|
|
@@ -2176,7 +2218,9 @@ export default {
|
|
|
2176
2218
|
:credential-id="credentialId"
|
|
2177
2219
|
:idx="idx"
|
|
2178
2220
|
:machine-pools="machinePools"
|
|
2221
|
+
:busy="busy"
|
|
2179
2222
|
@error="e=>errors = e"
|
|
2223
|
+
@validationChanged="v=>machinePoolValidationChanged(obj.id, v)"
|
|
2180
2224
|
/>
|
|
2181
2225
|
</Tab>
|
|
2182
2226
|
</template>
|
|
@@ -2653,6 +2697,7 @@ export default {
|
|
|
2653
2697
|
</Banner>
|
|
2654
2698
|
</div>
|
|
2655
2699
|
</div>
|
|
2700
|
+
|
|
2656
2701
|
<div
|
|
2657
2702
|
v-if="serverArgs['tls-san']"
|
|
2658
2703
|
class="row mb-20"
|
|
@@ -420,6 +420,7 @@ export default async function({
|
|
|
420
420
|
if ( e instanceof ClusterNotFoundError ) {
|
|
421
421
|
return redirect(302, '/home');
|
|
422
422
|
} else {
|
|
423
|
+
// Sets error 500 if lost connection to API
|
|
423
424
|
store.commit('setError', { error: e, locationError: new Error('Auth Middleware') });
|
|
424
425
|
|
|
425
426
|
return redirect(302, '/fail-whale');
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import Cronjob from '@shell/models/batch.cronjob';
|
|
2
|
+
describe('class Cronjob', () => {
|
|
3
|
+
it('should have no ownerReferences by default', () => {
|
|
4
|
+
const cronJobData = {
|
|
5
|
+
id: 'any-id',
|
|
6
|
+
type: 'batch.job',
|
|
7
|
+
apiVersion: 'batch/v1',
|
|
8
|
+
kind: 'Job',
|
|
9
|
+
metadata: {
|
|
10
|
+
name: 'any-name',
|
|
11
|
+
namespace: 'any-namespace',
|
|
12
|
+
uid: 'any-uid'
|
|
13
|
+
},
|
|
14
|
+
spec: { jobTemplate: {} }
|
|
15
|
+
};
|
|
16
|
+
const expectation = {
|
|
17
|
+
name: 'any-name', namespace: 'any-namespace', uid: 'any-uid'
|
|
18
|
+
};
|
|
19
|
+
const cronjob = new Cronjob(cronJobData);
|
|
20
|
+
|
|
21
|
+
expect(cronjob.metadata).toStrictEqual(expectation);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('method runNow', () => {
|
|
25
|
+
it('should populate job metadata', async() => {
|
|
26
|
+
const jobData = {
|
|
27
|
+
id: 'any-id',
|
|
28
|
+
type: 'batch.job',
|
|
29
|
+
apiVersion: 'batch/v1',
|
|
30
|
+
kind: 'Job',
|
|
31
|
+
metadata: {
|
|
32
|
+
name: 'any-name',
|
|
33
|
+
namespace: 'any-namespace',
|
|
34
|
+
uid: 'any-uid'
|
|
35
|
+
},
|
|
36
|
+
spec: { jobTemplate: {} }
|
|
37
|
+
};
|
|
38
|
+
const date = Date.now();
|
|
39
|
+
const expected = {
|
|
40
|
+
name: `${ jobData.metadata.name }-${ date }`,
|
|
41
|
+
namespace: jobData.metadata.namespace,
|
|
42
|
+
ownerReferences: [{
|
|
43
|
+
apiVersion: 'batch/v1',
|
|
44
|
+
controller: true,
|
|
45
|
+
kind: 'Job',
|
|
46
|
+
name: jobData.metadata.name,
|
|
47
|
+
uid: jobData.metadata.uid
|
|
48
|
+
}],
|
|
49
|
+
uid: jobData.metadata.uid
|
|
50
|
+
};
|
|
51
|
+
const dispatcher = () => ({
|
|
52
|
+
...jobData,
|
|
53
|
+
save: jest.fn(),
|
|
54
|
+
goToDetail: jest.fn()
|
|
55
|
+
});
|
|
56
|
+
const cronjob = new Cronjob(jobData, { dispatch: dispatcher });
|
|
57
|
+
|
|
58
|
+
jest
|
|
59
|
+
.useFakeTimers()
|
|
60
|
+
.setSystemTime(date);
|
|
61
|
+
jest.spyOn(cronjob, '$dispatch').mockImplementation(dispatcher);
|
|
62
|
+
|
|
63
|
+
await cronjob.runNow();
|
|
64
|
+
|
|
65
|
+
expect(cronjob.metadata).toStrictEqual(expected);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should redirect to another page', async() => {
|
|
69
|
+
const jobData = {
|
|
70
|
+
metadata: { name: 'any-name' },
|
|
71
|
+
spec: { jobTemplate: {} }
|
|
72
|
+
};
|
|
73
|
+
const callback = jest.fn();
|
|
74
|
+
const dispatcher = () => ({
|
|
75
|
+
...jobData,
|
|
76
|
+
save: jest.fn(),
|
|
77
|
+
goToDetail: callback
|
|
78
|
+
});
|
|
79
|
+
const cronjob = new Cronjob(jobData, { dispatch: dispatcher });
|
|
80
|
+
|
|
81
|
+
jest.spyOn(cronjob, '$dispatch').mockImplementation(dispatcher);
|
|
82
|
+
|
|
83
|
+
await cronjob.runNow();
|
|
84
|
+
|
|
85
|
+
expect(callback).toHaveBeenCalledWith();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
package/models/cluster/node.js
CHANGED
|
@@ -209,6 +209,14 @@ export default class ClusterNode extends SteveModel {
|
|
|
209
209
|
return ((this.ramUsage * 100) / this.ramCapacity).toString();
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
get ramReserved() {
|
|
213
|
+
return parseSi(this.status?.allocatable?.memory);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get ramReservedPercentage() {
|
|
217
|
+
return ((this.ramUsage * 100) / this.ramReserved).toString();
|
|
218
|
+
}
|
|
219
|
+
|
|
212
220
|
get podUsage() {
|
|
213
221
|
return calculatePercentage(this.status.allocatable.pods, this.status.capacity.pods);
|
|
214
222
|
}
|
|
@@ -37,13 +37,17 @@ export default class CRTB extends HybridModel {
|
|
|
37
37
|
}, { root: true });
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
get syncPrincipal() {
|
|
41
|
+
return this.$rootGetters['rancher/byId'](NORMAN.PRINCIPAL, this.principalId);
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
get principalId() {
|
|
41
45
|
// We've either set it ourselves or it's comes from native properties
|
|
42
46
|
return this.principalName || this.userPrincipalName || this.groupPrincipalName;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
get nameDisplay() {
|
|
46
|
-
return this.user?.nameDisplay || this.userName || this.principalId;
|
|
50
|
+
return this.user?.nameDisplay || this.userName || this.syncPrincipal?.nameDisplay || this.principalId;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
get roleDisplay() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MANAGEMENT } from '@shell/config/types';
|
|
1
|
+
import { MANAGEMENT, NORMAN } from '@shell/config/types';
|
|
2
2
|
import NormanModel from '@shell/plugins/steve/norman-class';
|
|
3
3
|
|
|
4
4
|
export default class PRTB extends NormanModel {
|
|
@@ -24,4 +24,12 @@ export default class PRTB extends NormanModel {
|
|
|
24
24
|
id: this.id?.replace(':', '/')
|
|
25
25
|
}, { root: true });
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
get syncPrincipal() {
|
|
29
|
+
return this.$rootGetters['rancher/byId'](NORMAN.PRINCIPAL, this.principalId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get nameDisplay() {
|
|
33
|
+
return this.syncPrincipal?.nameDisplay || super.nameDisplay;
|
|
34
|
+
}
|
|
27
35
|
}
|
package/models/workload.js
CHANGED
|
@@ -4,7 +4,7 @@ import { WORKLOAD_TYPES, SERVICE, POD } from '@shell/config/types';
|
|
|
4
4
|
import { get, set } from '@shell/utils/object';
|
|
5
5
|
import day from 'dayjs';
|
|
6
6
|
import { convertSelectorObj, matching, matches } from '@shell/utils/selector';
|
|
7
|
-
import { SEPARATOR } from '@shell/
|
|
7
|
+
import { SEPARATOR } from '@shell/config/workload';
|
|
8
8
|
import WorkloadService from '@shell/models/workload.service';
|
|
9
9
|
|
|
10
10
|
export const defaultContainer = {
|
package/package.json
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import Preferences from '@shell/pages/prefs.vue';
|
|
3
|
+
|
|
4
|
+
describe('page: prefs should', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
['Fri, May 5 2023', 0],
|
|
7
|
+
['Fri, 5 May 2023', 1],
|
|
8
|
+
['5/5/2023 (D/M/YYYY)', 2],
|
|
9
|
+
['5/5/2023 (M/D/YYYY)', 3],
|
|
10
|
+
['2023-05-05 (YYYY-MM-DD)', 4],
|
|
11
|
+
])('format the date optionally with a cue as %p for date 05/05/2023', (expected, index) => {
|
|
12
|
+
const date = '05/05/2023';
|
|
13
|
+
const format = ['ddd, MMM D YYYY', 'ddd, D MMM YYYY', 'D/M/YYYY', 'M/D/YYYY', 'YYYY-MM-DD'];
|
|
14
|
+
|
|
15
|
+
jest
|
|
16
|
+
.useFakeTimers()
|
|
17
|
+
.setSystemTime(new Date(date));
|
|
18
|
+
|
|
19
|
+
const wrapper = shallowMount(Preferences, {
|
|
20
|
+
propsData: { value: 'asd' },
|
|
21
|
+
computed: {
|
|
22
|
+
pm: jest.fn(),
|
|
23
|
+
am: jest.fn(),
|
|
24
|
+
},
|
|
25
|
+
mocks: {
|
|
26
|
+
$store: {
|
|
27
|
+
getters: {
|
|
28
|
+
'prefs/options': () => format,
|
|
29
|
+
'prefs/get': jest.fn(),
|
|
30
|
+
'management/schemaFor': jest.fn(),
|
|
31
|
+
isSingleProduct: jest.fn(),
|
|
32
|
+
'i18n/t': jest.fn(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
stubs: {
|
|
37
|
+
BackLink: { template: '<div />' },
|
|
38
|
+
ButtonGroup: { template: '<div />' },
|
|
39
|
+
LabeledSelect: { template: '<div />' },
|
|
40
|
+
Checkbox: { template: '<div />' },
|
|
41
|
+
LandingPagePreference: { template: '<div />' },
|
|
42
|
+
LocaleSelector: { template: '<div />' },
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const options = (wrapper.vm as unknown as any).dateOptions;
|
|
47
|
+
|
|
48
|
+
expect(options[index].label).toBe(expected);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it.each([
|
|
52
|
+
['Mon, May 1 2023', 0],
|
|
53
|
+
['Mon, 1 May 2023', 1],
|
|
54
|
+
['1/5/2023', 2],
|
|
55
|
+
['5/1/2023', 3],
|
|
56
|
+
['2023-05-01', 4],
|
|
57
|
+
])('format the date %p without cue for date 01/05/2023', (expected, index) => {
|
|
58
|
+
const date = '05/01/2023';
|
|
59
|
+
const format = ['ddd, MMM D YYYY', 'ddd, D MMM YYYY', 'D/M/YYYY', 'M/D/YYYY', 'YYYY-MM-DD'];
|
|
60
|
+
|
|
61
|
+
jest
|
|
62
|
+
.useFakeTimers()
|
|
63
|
+
.setSystemTime(new Date(date));
|
|
64
|
+
|
|
65
|
+
const wrapper = shallowMount(Preferences, {
|
|
66
|
+
propsData: { value: 'asd' },
|
|
67
|
+
computed: {
|
|
68
|
+
pm: jest.fn(),
|
|
69
|
+
am: jest.fn(),
|
|
70
|
+
},
|
|
71
|
+
mocks: {
|
|
72
|
+
$store: {
|
|
73
|
+
getters: {
|
|
74
|
+
'prefs/options': () => format,
|
|
75
|
+
'prefs/get': jest.fn(),
|
|
76
|
+
'management/schemaFor': jest.fn(),
|
|
77
|
+
isSingleProduct: jest.fn(),
|
|
78
|
+
'i18n/t': jest.fn(),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
stubs: {
|
|
83
|
+
BackLink: { template: '<div />' },
|
|
84
|
+
ButtonGroup: { template: '<div />' },
|
|
85
|
+
LabeledSelect: { template: '<div />' },
|
|
86
|
+
Checkbox: { template: '<div />' },
|
|
87
|
+
LandingPagePreference: { template: '<div />' },
|
|
88
|
+
LocaleSelector: { template: '<div />' },
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const options = (wrapper.vm as unknown as any).dateOptions;
|
|
93
|
+
|
|
94
|
+
expect(options[index].label).toBe(expected);
|
|
95
|
+
});
|
|
96
|
+
});
|
package/pages/auth/setup.vue
CHANGED
|
@@ -16,6 +16,7 @@ import { isDevBuild } from '@shell/utils/version';
|
|
|
16
16
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
17
17
|
import Password from '@shell/components/form/Password';
|
|
18
18
|
import { applyProducts } from '@shell/store/type-map';
|
|
19
|
+
import BrandImage from '@shell/components/BrandImage';
|
|
19
20
|
|
|
20
21
|
const calcIsFirstLogin = (store) => {
|
|
21
22
|
const firstLoginSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FIRST_LOGIN);
|
|
@@ -76,7 +77,7 @@ export default {
|
|
|
76
77
|
},
|
|
77
78
|
|
|
78
79
|
components: {
|
|
79
|
-
AsyncButton, LabeledInput, CopyToClipboard, Checkbox, RadioGroup, Password
|
|
80
|
+
AsyncButton, LabeledInput, CopyToClipboard, Checkbox, RadioGroup, Password, BrandImage
|
|
80
81
|
},
|
|
81
82
|
|
|
82
83
|
async asyncData({ route, req, store }) {
|
|
@@ -284,7 +285,6 @@ export default {
|
|
|
284
285
|
v-clean-html="t(isFirstLogin ? 'setup.setPassword' : 'setup.newUserSetPassword', { username }, true)"
|
|
285
286
|
class="text-center mb-20 mt-20 setup-title"
|
|
286
287
|
/>
|
|
287
|
-
|
|
288
288
|
<Password
|
|
289
289
|
v-if="!haveCurrent"
|
|
290
290
|
v-model.trim="current"
|
|
@@ -431,8 +431,10 @@ export default {
|
|
|
431
431
|
</div>
|
|
432
432
|
</div>
|
|
433
433
|
</div>
|
|
434
|
-
|
|
435
|
-
|
|
434
|
+
<BrandImage
|
|
435
|
+
class="col span-6 landscape"
|
|
436
|
+
file-name="login-landscape.svg"
|
|
437
|
+
/>
|
|
436
438
|
</div>
|
|
437
439
|
</form>
|
|
438
440
|
</template>
|
|
@@ -464,6 +466,13 @@ export default {
|
|
|
464
466
|
.span-6 {
|
|
465
467
|
padding: 0 60px;
|
|
466
468
|
}
|
|
469
|
+
|
|
470
|
+
.landscape {
|
|
471
|
+
height: 100vh;
|
|
472
|
+
margin: 0;
|
|
473
|
+
object-fit: cover;
|
|
474
|
+
padding: 0;
|
|
475
|
+
}
|
|
467
476
|
}
|
|
468
477
|
|
|
469
478
|
.form-col {
|
|
@@ -491,14 +500,5 @@ export default {
|
|
|
491
500
|
p {
|
|
492
501
|
line-height: 20px;
|
|
493
502
|
}
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
.landscape {
|
|
498
|
-
background-image: url('~@shell/assets/images/pl/login-landscape.svg');
|
|
499
|
-
background-repeat: no-repeat;
|
|
500
|
-
background-size: cover;
|
|
501
|
-
background-position: center center;
|
|
502
|
-
height: 100vh;
|
|
503
503
|
}
|
|
504
504
|
</style>
|
|
@@ -116,7 +116,6 @@ export default {
|
|
|
116
116
|
<Loading v-if="$fetchState.pending" />
|
|
117
117
|
<div v-else>
|
|
118
118
|
<TypeDescription resource="chart" />
|
|
119
|
-
|
|
120
119
|
<div
|
|
121
120
|
v-if="chart"
|
|
122
121
|
class="chart-header"
|
|
@@ -139,6 +138,7 @@ export default {
|
|
|
139
138
|
<button
|
|
140
139
|
v-if="!requires.length"
|
|
141
140
|
type="button"
|
|
141
|
+
data-testid="btn-chart-install"
|
|
142
142
|
class="btn role-primary"
|
|
143
143
|
@click.prevent="install"
|
|
144
144
|
>
|