@rancher/shell 3.0.12-rc.4 → 3.0.12-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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +49 -14
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/ResourceTable.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/Wizard.vue +14 -1
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/form/FileSelector.vue +39 -1
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/Security.vue +1 -1
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/Header.vue +8 -1
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +3 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/table-headers.js +5 -2
- package/config/types.js +7 -0
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +79 -5
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +51 -5
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +6 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +25 -12
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +12 -7
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +569 -81
- package/pages/c/_cluster/apps/charts/install.vue +143 -28
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +62 -15
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +5 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +120 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/operation-cr.js +19 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -40,9 +40,12 @@ import {
|
|
|
40
40
|
import { ignoreVariables } from './install.helpers';
|
|
41
41
|
import { findBy, insertAt } from '@shell/utils/array';
|
|
42
42
|
import { saferDump } from '@shell/utils/create-yaml';
|
|
43
|
+
import { addParam } from '@shell/utils/url';
|
|
43
44
|
import { WINDOWS } from '@shell/store/catalog';
|
|
44
45
|
import { SETTING } from '@shell/config/settings';
|
|
45
46
|
import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret.vue';
|
|
47
|
+
import PrivateRegistry from '@shell/components/form/PrivateRegistry.vue';
|
|
48
|
+
import { PRIVATE_REGISTRY_CONTEXT } from '@shell/components/form/PrivateRegistry.constants';
|
|
46
49
|
import { generateRandomAlphaString } from '@shell/utils/string';
|
|
47
50
|
|
|
48
51
|
const VALUES_STATE = {
|
|
@@ -95,7 +98,8 @@ export default {
|
|
|
95
98
|
UnitInput,
|
|
96
99
|
YamlEditor,
|
|
97
100
|
Wizard,
|
|
98
|
-
SelectOrCreateAuthSecret
|
|
101
|
+
SelectOrCreateAuthSecret,
|
|
102
|
+
PrivateRegistry
|
|
99
103
|
},
|
|
100
104
|
|
|
101
105
|
mixins: [
|
|
@@ -357,6 +361,15 @@ export default {
|
|
|
357
361
|
this.showCustomRegistryInput = !!this.customRegistrySetting;
|
|
358
362
|
}
|
|
359
363
|
|
|
364
|
+
// On upgrade, pre-select a single existing image pull secret in the dropdown
|
|
365
|
+
if (this.existing && this.showRegistryPullSecrets) {
|
|
366
|
+
const existingPullSecrets = this.chartValues?.global?.imagePullSecrets;
|
|
367
|
+
|
|
368
|
+
if (Array.isArray(existingPullSecrets) && existingPullSecrets.length === 1) {
|
|
369
|
+
this.registryPullSecret = existingPullSecrets[0];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
360
373
|
/* Serializes an object as a YAML document */
|
|
361
374
|
this.valuesYaml = saferDump(this.chartValues);
|
|
362
375
|
|
|
@@ -453,6 +466,9 @@ export default {
|
|
|
453
466
|
appCoDataFetched: false,
|
|
454
467
|
AUTH_TYPE,
|
|
455
468
|
CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME,
|
|
469
|
+
PRIVATE_REGISTRY_CONTEXT,
|
|
470
|
+
skipPullSecrets: false,
|
|
471
|
+
registryPullSecret: null,
|
|
456
472
|
stepBasic: {
|
|
457
473
|
name: 'basics',
|
|
458
474
|
label: this.t('catalog.install.steps.basics.label'),
|
|
@@ -499,6 +515,29 @@ export default {
|
|
|
499
515
|
...mapGetters({ inStore: 'catalog/inStore', features: 'features/get' }),
|
|
500
516
|
mcm: mapFeature(MULTI_CLUSTER),
|
|
501
517
|
|
|
518
|
+
showMonitoringBanner() {
|
|
519
|
+
const annotations = this.version?.annotations || {};
|
|
520
|
+
const releaseName = annotations[CATALOG_ANNOTATIONS.RELEASE_NAME];
|
|
521
|
+
const certified = annotations[CATALOG_ANNOTATIONS.CERTIFIED];
|
|
522
|
+
|
|
523
|
+
// Only show monitoring banners for the Rancher-certified charts —
|
|
524
|
+
// a third-party chart that happens to reuse the release name should not
|
|
525
|
+
// trigger our migration prompts.
|
|
526
|
+
if (certified !== 'rancher') {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (releaseName === 'rancher-monitoring') {
|
|
531
|
+
return this.t('catalog.install.steps.basics.oldMonitoringChartWarning');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!this.existing && releaseName === 'rancher-monitoring-dashboards') {
|
|
535
|
+
return this.t('catalog.install.steps.basics.newMonitoringChartWarning');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return null;
|
|
539
|
+
},
|
|
540
|
+
|
|
502
541
|
/**
|
|
503
542
|
* Return list of variables to filter chart questions
|
|
504
543
|
*/
|
|
@@ -783,6 +822,29 @@ export default {
|
|
|
783
822
|
return global.systemDefaultRegistry !== undefined || global.cattle?.systemDefaultRegistry !== undefined;
|
|
784
823
|
},
|
|
785
824
|
|
|
825
|
+
showRegistryPullSecrets() {
|
|
826
|
+
return !!this.repo?.spec?.defaultImagePullSecrets?.length;
|
|
827
|
+
},
|
|
828
|
+
|
|
829
|
+
existingValuesPullSecrets() {
|
|
830
|
+
if (!this.existing) {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const pullSecrets = this.chartValues?.global?.imagePullSecrets;
|
|
835
|
+
|
|
836
|
+
return Array.isArray(pullSecrets) ? pullSecrets.filter(Boolean) : [];
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* if the system-default-pull-image-secrets global setting is set OR the current cluster has system default registry pull secrets configured
|
|
841
|
+
* the Rancher cluster repo will automatically be populated with
|
|
842
|
+
* copies of the secrets referenced in the global setting
|
|
843
|
+
*/
|
|
844
|
+
repoDefaultPullSecretNames() {
|
|
845
|
+
return (this.repo?.spec?.defaultImagePullSecrets || []).map((s) => s.name).filter(Boolean);
|
|
846
|
+
},
|
|
847
|
+
|
|
786
848
|
setImagePullSecretDataTrigger() {
|
|
787
849
|
return `
|
|
788
850
|
${ this.defaultImagePullSecret?.name }
|
|
@@ -974,11 +1036,30 @@ export default {
|
|
|
974
1036
|
}
|
|
975
1037
|
}
|
|
976
1038
|
},
|
|
1039
|
+
|
|
977
1040
|
async getClusterRegistry() {
|
|
1041
|
+
const mgmCluster = this.$store.getters['currentCluster'];
|
|
1042
|
+
|
|
1043
|
+
// For local, imported, and hosted (AKS, EKS, GKE, ALI) clusters,
|
|
1044
|
+
// the cluster-scoped private registry is on the norman cluster's importedConfig.
|
|
1045
|
+
if (mgmCluster?.isLocal || mgmCluster?.isImported || mgmCluster?.isHostedKubernetesProvider) {
|
|
1046
|
+
try {
|
|
1047
|
+
const normanCluster = await mgmCluster.findNormanCluster();
|
|
1048
|
+
const importedRegistryURL = normanCluster?.importedConfig?.privateRegistryURL;
|
|
1049
|
+
|
|
1050
|
+
if (importedRegistryURL) {
|
|
1051
|
+
return importedRegistryURL;
|
|
1052
|
+
}
|
|
1053
|
+
} catch (e) {
|
|
1054
|
+
console.warn('Unable to fetch norman cluster for registry lookup: ', e); // eslint-disable-line no-console
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
978
1060
|
const hasPermissionToSeeProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
|
|
979
1061
|
|
|
980
1062
|
if (hasPermissionToSeeProvCluster) {
|
|
981
|
-
const mgmCluster = this.$store.getters['currentCluster'];
|
|
982
1063
|
const provClusterId = mgmCluster?.provClusterId;
|
|
983
1064
|
let provCluster;
|
|
984
1065
|
|
|
@@ -1148,14 +1229,22 @@ export default {
|
|
|
1148
1229
|
const isUpgrade = !!this.existing;
|
|
1149
1230
|
|
|
1150
1231
|
this.errors = [];
|
|
1232
|
+
// Create namespace if it doesn't exist
|
|
1233
|
+
// this is done before save hooks so that image pull secrets can be created in the target namespace
|
|
1234
|
+
await this.createNamespaceIfNeeded();
|
|
1151
1235
|
|
|
1152
|
-
|
|
1153
|
-
// And only if it is SUSE APP Collection, overall should just do the same flow
|
|
1154
|
-
if (!isUpgrade && this.isNamespaceNew && this.repo?.isSuseAppCollection) {
|
|
1155
|
-
await this.createNamespaceIfNeeded();
|
|
1156
|
-
}
|
|
1236
|
+
const hookResults = await this.applyHooks(BEFORE_SAVE_HOOKS);
|
|
1157
1237
|
|
|
1158
|
-
|
|
1238
|
+
// When a new pull secret is created by SelectOrCreateAuthSecret inside
|
|
1239
|
+
// PrivateRegistry, the emit chain does not propagate the secret name
|
|
1240
|
+
// back to registryPullSecret in time. Read it from the hook result.
|
|
1241
|
+
if (this.showRegistryPullSecrets && !this.skipPullSecrets && !this.registryPullSecret) {
|
|
1242
|
+
const createdSecret = hookResults?.registerAuthSecret;
|
|
1243
|
+
|
|
1244
|
+
if (createdSecret?.metadata?.name) {
|
|
1245
|
+
this.registryPullSecret = createdSecret.metadata.name;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1159
1248
|
|
|
1160
1249
|
const { errors, input } = this.actionInput(isUpgrade);
|
|
1161
1250
|
|
|
@@ -1166,7 +1255,16 @@ export default {
|
|
|
1166
1255
|
return;
|
|
1167
1256
|
}
|
|
1168
1257
|
|
|
1169
|
-
const
|
|
1258
|
+
const actionName = isUpgrade ? 'upgrade' : 'install';
|
|
1259
|
+
const actionOpt = {};
|
|
1260
|
+
|
|
1261
|
+
if (this.skipPullSecrets) {
|
|
1262
|
+
const baseUrl = this.repo.actionLinkFor(actionName);
|
|
1263
|
+
|
|
1264
|
+
actionOpt.url = addParam(baseUrl, 'skipPullSecrets', 'true');
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const res = await this.repo.doAction(actionName, input, actionOpt);
|
|
1170
1268
|
const operationId = `${ res.operationNamespace }/${ res.operationName }`;
|
|
1171
1269
|
|
|
1172
1270
|
// Non-admins without a cluster won't be able to fetch operations immediately
|
|
@@ -1227,6 +1325,15 @@ export default {
|
|
|
1227
1325
|
set(global, 'systemDefaultRegistry', this.customRegistrySetting);
|
|
1228
1326
|
}
|
|
1229
1327
|
|
|
1328
|
+
if (this.showRegistryPullSecrets && this.registryPullSecret) {
|
|
1329
|
+
// User explicitly selected or created a pull secret
|
|
1330
|
+
set(global, 'imagePullSecrets', [this.registryPullSecret]);
|
|
1331
|
+
} else if (this.showRegistryPullSecrets) {
|
|
1332
|
+
// User chose "skip" or "use default" — remove explicit imagePullSecrets
|
|
1333
|
+
// so the backend falls back to the repo/global defaults
|
|
1334
|
+
delete global.imagePullSecrets;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1230
1337
|
setIfNotSet(global, 'cattle.systemProjectId', systemProjectId);
|
|
1231
1338
|
setIfNotSet(cattle, 'url', serverUrl);
|
|
1232
1339
|
setIfNotSet(cattle, 'rkePathPrefix', pathPrefix);
|
|
@@ -1329,7 +1436,6 @@ export default {
|
|
|
1329
1436
|
*/
|
|
1330
1437
|
|
|
1331
1438
|
this.addGlobalValuesTo(values);
|
|
1332
|
-
|
|
1333
1439
|
const form = JSON.parse(JSON.stringify(this.value));
|
|
1334
1440
|
|
|
1335
1441
|
/*
|
|
@@ -1488,6 +1594,8 @@ export default {
|
|
|
1488
1594
|
}
|
|
1489
1595
|
},
|
|
1490
1596
|
|
|
1597
|
+
// not the same as PrivateRegistry pull secrets which are created based off the global/cluster system default registry hostname value not the repo url directly
|
|
1598
|
+
// those secrets will be created in a beforeSaveHook managed by SelectOrCreateAuthSecret
|
|
1491
1599
|
async createImagePullSecret() {
|
|
1492
1600
|
if (!this.repo?.isSuseAppCollection) {
|
|
1493
1601
|
return;
|
|
@@ -1593,6 +1701,12 @@ export default {
|
|
|
1593
1701
|
</template>
|
|
1594
1702
|
<template #basics>
|
|
1595
1703
|
<div class="step__basic">
|
|
1704
|
+
<Banner
|
|
1705
|
+
v-if="showMonitoringBanner"
|
|
1706
|
+
color="warning"
|
|
1707
|
+
>
|
|
1708
|
+
{{ showMonitoringBanner }}
|
|
1709
|
+
</Banner>
|
|
1596
1710
|
<Banner
|
|
1597
1711
|
v-if="step1Description"
|
|
1598
1712
|
color="info"
|
|
@@ -1753,26 +1867,27 @@ export default {
|
|
|
1753
1867
|
:label="t('catalog.install.steps.helmCli.checkbox', { action: action.name, existing: !!existing })"
|
|
1754
1868
|
/>
|
|
1755
1869
|
|
|
1756
|
-
<
|
|
1870
|
+
<PrivateRegistry
|
|
1757
1871
|
v-if="showCustomRegistry"
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
:
|
|
1762
|
-
:
|
|
1872
|
+
:context="PRIVATE_REGISTRY_CONTEXT.CHARTS"
|
|
1873
|
+
:value="customRegistrySetting"
|
|
1874
|
+
:enabled="showCustomRegistryInput"
|
|
1875
|
+
:default-registry="defaultRegistrySetting"
|
|
1876
|
+
:namespace="targetNamespace"
|
|
1877
|
+
in-store="cluster"
|
|
1878
|
+
:register-before-hook="registerBeforeHook"
|
|
1879
|
+
:show-pull-secrets="showRegistryPullSecrets"
|
|
1880
|
+
:repo-default-pull-secrets="repoDefaultPullSecretNames"
|
|
1881
|
+
:existing-values-pull-secrets="existingValuesPullSecrets"
|
|
1882
|
+
:pull-secret="registryPullSecret"
|
|
1883
|
+
:skip-pull-secrets="skipPullSecrets"
|
|
1884
|
+
checkbox-test-id="custom-registry-checkbox"
|
|
1885
|
+
input-test-id="custom-registry-input"
|
|
1886
|
+
@update:value="(val) => customRegistrySetting = val"
|
|
1887
|
+
@update:enabled="(val) => showCustomRegistryInput = val"
|
|
1888
|
+
@update:pull-secret="(val) => registryPullSecret = val"
|
|
1889
|
+
@update:skip-pull-secrets="(val) => skipPullSecrets = val"
|
|
1763
1890
|
/>
|
|
1764
|
-
<div class="row">
|
|
1765
|
-
<div class="col span-6">
|
|
1766
|
-
<LabeledInput
|
|
1767
|
-
v-if="showCustomRegistryInput"
|
|
1768
|
-
v-model:value="customRegistrySetting"
|
|
1769
|
-
data-testid="custom-registry-input"
|
|
1770
|
-
label-key="catalog.chart.registry.custom.inputLabel"
|
|
1771
|
-
placeholder-key="catalog.chart.registry.custom.placeholder"
|
|
1772
|
-
:min-height="30"
|
|
1773
|
-
/>
|
|
1774
|
-
</div>
|
|
1775
|
-
</div>
|
|
1776
1891
|
<div
|
|
1777
1892
|
class="step__values__controls--spacer"
|
|
1778
1893
|
style="flex:1"
|
|
@@ -6,6 +6,7 @@ import { MODE, _EDIT } from '@shell/config/query-params';
|
|
|
6
6
|
import { authProvidersInfo } from '@shell/utils/auth';
|
|
7
7
|
import { Banner } from '@components/Banner';
|
|
8
8
|
import Loading from '@shell/components/Loading';
|
|
9
|
+
import { HIDE_LOCAL_AUTH_PROVIDER } from '@shell/store/features';
|
|
9
10
|
|
|
10
11
|
const resource = MANAGEMENT.AUTH_CONFIG;
|
|
11
12
|
|
|
@@ -28,8 +29,9 @@ export default {
|
|
|
28
29
|
data() {
|
|
29
30
|
return {
|
|
30
31
|
// Provided by fetch later
|
|
31
|
-
enabled:
|
|
32
|
-
nonLocal:
|
|
32
|
+
enabled: false,
|
|
33
|
+
nonLocal: null,
|
|
34
|
+
disableLocalAuth: this.$store.getters['features/get'](HIDE_LOCAL_AUTH_PROVIDER)
|
|
33
35
|
};
|
|
34
36
|
},
|
|
35
37
|
|
|
@@ -100,8 +102,13 @@ export default {
|
|
|
100
102
|
<router-link :to="localUsersRoute">
|
|
101
103
|
{{ t('authConfig.manageLocal') }}
|
|
102
104
|
</router-link>
|
|
103
|
-
<br>
|
|
104
|
-
|
|
105
|
+
<br><br>
|
|
106
|
+
<template v-if="disableLocalAuth">
|
|
107
|
+
{{ t('authConfig.bannerEnableAuthProvider', null, true) }}
|
|
108
|
+
</template>
|
|
109
|
+
<template v-else>
|
|
110
|
+
{{ t('authConfig.noneEnabled') }}
|
|
111
|
+
</template>
|
|
105
112
|
</div>
|
|
106
113
|
</Banner>
|
|
107
114
|
<SelectIconGrid
|
|
@@ -106,7 +106,12 @@ export default {
|
|
|
106
106
|
const grafanaMatch = findBy(externalLinks, 'group', 'grafana');
|
|
107
107
|
// Generate Grafana link
|
|
108
108
|
const currentCluster = this.$store.getters['currentCluster'];
|
|
109
|
-
|
|
109
|
+
let rancherMonitoring = !isEmpty(res.apps) ? findBy(res.apps, 'id', 'cattle-monitoring-system/rancher-monitoring-dashboards') : '';
|
|
110
|
+
|
|
111
|
+
if (!rancherMonitoring) {
|
|
112
|
+
rancherMonitoring = !isEmpty(res.apps) ? findBy(res.apps, 'id', 'cattle-monitoring-system/rancher-monitoring') : '';
|
|
113
|
+
}
|
|
114
|
+
|
|
110
115
|
const clusterPrefix = getClusterPrefix(rancherMonitoring?.currentVersion || '', currentCluster.id);
|
|
111
116
|
|
|
112
117
|
grafanaMatch.link = `${ clusterPrefix }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Stub for extension library builds — prevents require.context() from bundling
|
|
2
|
+
// all shell images into every extension. At runtime, delegates to the host
|
|
3
|
+
// dashboard's asset resolver (exposed on window by the real require-asset.ts).
|
|
4
|
+
|
|
5
|
+
export function toContextKey(path) {
|
|
6
|
+
return `./${ path.replace(/^[~@]shell\/assets\//, '') }`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function requireAsset(path) {
|
|
10
|
+
if (typeof window !== 'undefined' && window.__shell_requireAsset) {
|
|
11
|
+
return window.__shell_requireAsset(path);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
throw new Error(`Asset context not available for: ${ path }`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function requireJson(path) {
|
|
18
|
+
if (typeof window !== 'undefined' && window.__shell_requireJson) {
|
|
19
|
+
return window.__shell_requireJson(path);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
throw new Error(`JSON context not available for: ${ path }`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function _setContexts() {}
|
package/pkg/vue.config.js
CHANGED
|
@@ -72,11 +72,18 @@ module.exports = function(dir) {
|
|
|
72
72
|
resource.request = fs.existsSync(pkgModelLoaderRequire) ? pkgModelLoaderRequire : path.join(__dirname, fileName);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// Prevent require.context('@shell/assets') from bundling all shell images into extensions.
|
|
76
|
+
// The stub delegates to the host dashboard's asset resolver at runtime via window.__shell_requireAsset.
|
|
77
|
+
const requireAssetOverride = new webpack.NormalModuleReplacementPlugin(/require-asset$/, (resource) => {
|
|
78
|
+
resource.request = path.join(__dirname, 'require-asset.lib.js');
|
|
79
|
+
});
|
|
80
|
+
|
|
75
81
|
// Auto-generate module to import the types (model, detail, edit etc)
|
|
76
82
|
const autoImportPlugin = new VirtualModulesPlugin({ 'node_modules/@rancher/auto-import': generateTypeImport('@pkg', dir) });
|
|
77
83
|
|
|
78
84
|
config.plugins.unshift(dynamicImporterOverride);
|
|
79
85
|
config.plugins.unshift(modelLoaderImporterOverride);
|
|
86
|
+
config.plugins.unshift(requireAssetOverride);
|
|
80
87
|
config.plugins.unshift(autoImportPlugin);
|
|
81
88
|
config.plugins.unshift(new NodePolyfillPlugin()); // required from Webpack 5 to polyfill node modules
|
|
82
89
|
// config.plugins.unshift(debug);
|
|
@@ -678,4 +678,88 @@ describe('class: Resource', () => {
|
|
|
678
678
|
expect(viewYaml.enabled).toBe(true);
|
|
679
679
|
});
|
|
680
680
|
});
|
|
681
|
+
|
|
682
|
+
describe('method: dryRunCreate', () => {
|
|
683
|
+
const collectionUrl = '/v1/test.resources';
|
|
684
|
+
|
|
685
|
+
it('should dispatch a request with dryRun=All query param', async() => {
|
|
686
|
+
const dispatch = jest.fn().mockResolvedValue({});
|
|
687
|
+
const resource = new Resource({
|
|
688
|
+
type: 'test.resource',
|
|
689
|
+
metadata: {
|
|
690
|
+
name: 'my-resource',
|
|
691
|
+
namespace: 'my-ns',
|
|
692
|
+
},
|
|
693
|
+
}, {
|
|
694
|
+
getters: {
|
|
695
|
+
schemaFor: () => ({
|
|
696
|
+
linkFor: (link: string) => (link === 'collection' ? collectionUrl : ''),
|
|
697
|
+
attributes: { namespaced: true },
|
|
698
|
+
})
|
|
699
|
+
},
|
|
700
|
+
dispatch,
|
|
701
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
await resource.dryRunCreate();
|
|
705
|
+
|
|
706
|
+
expect(dispatch).toHaveBeenCalledWith('request', {
|
|
707
|
+
opt: expect.objectContaining({
|
|
708
|
+
method: 'post',
|
|
709
|
+
url: `${ collectionUrl }/my-ns?dryRun=All`,
|
|
710
|
+
}),
|
|
711
|
+
type: 'test.resource'
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('should use provided data instead of resource state when given', async() => {
|
|
716
|
+
const dispatch = jest.fn().mockResolvedValue({});
|
|
717
|
+
const resource = new Resource({
|
|
718
|
+
type: 'test.resource',
|
|
719
|
+
metadata: { name: 'original', namespace: 'ns' },
|
|
720
|
+
}, {
|
|
721
|
+
getters: {
|
|
722
|
+
schemaFor: () => ({
|
|
723
|
+
linkFor: () => collectionUrl,
|
|
724
|
+
attributes: { namespaced: true },
|
|
725
|
+
})
|
|
726
|
+
},
|
|
727
|
+
dispatch,
|
|
728
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
const customData = {
|
|
732
|
+
type: 'test.resource',
|
|
733
|
+
metadata: { name: 'custom' },
|
|
734
|
+
spec: {}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
await resource.dryRunCreate(customData);
|
|
738
|
+
|
|
739
|
+
expect(dispatch).toHaveBeenCalledWith('request', {
|
|
740
|
+
opt: expect.objectContaining({ data: customData }),
|
|
741
|
+
type: 'test.resource'
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('should propagate API errors', async() => {
|
|
746
|
+
const apiError = { _status: 409, message: 'already exists' };
|
|
747
|
+
const dispatch = jest.fn().mockRejectedValue(apiError);
|
|
748
|
+
const resource = new Resource({
|
|
749
|
+
type: 'test.resource',
|
|
750
|
+
metadata: { name: 'dup', namespace: 'ns' },
|
|
751
|
+
}, {
|
|
752
|
+
getters: {
|
|
753
|
+
schemaFor: () => ({
|
|
754
|
+
linkFor: () => collectionUrl,
|
|
755
|
+
attributes: { namespaced: true },
|
|
756
|
+
})
|
|
757
|
+
},
|
|
758
|
+
dispatch,
|
|
759
|
+
rootGetters: { 'i18n/t': jest.fn() },
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
await expect(resource.dryRunCreate()).rejects.toStrictEqual(apiError);
|
|
763
|
+
});
|
|
764
|
+
});
|
|
681
765
|
});
|
|
@@ -79,6 +79,7 @@ export const STATES_ENUM = {
|
|
|
79
79
|
BUILDING: 'building',
|
|
80
80
|
COMPLETED: 'completed',
|
|
81
81
|
CORDONED: 'cordoned',
|
|
82
|
+
CANCELLED: 'cancelled',
|
|
82
83
|
COUNT: 'count',
|
|
83
84
|
CREATED: 'created',
|
|
84
85
|
CREATING: 'creating',
|
|
@@ -207,6 +208,9 @@ export const STATES = {
|
|
|
207
208
|
[STATES_ENUM.CORDONED]: {
|
|
208
209
|
color: 'info', icon: 'tag', label: 'Cordoned', compoundIcon: 'info'
|
|
209
210
|
},
|
|
211
|
+
[STATES_ENUM.CANCELLED]: {
|
|
212
|
+
color: 'warning', icon: 'error', label: 'Cancelled', compoundIcon: 'warning'
|
|
213
|
+
},
|
|
210
214
|
[STATES_ENUM.COUNT]: {
|
|
211
215
|
color: 'success', icon: 'dot-open', label: 'Count', compoundIcon: 'checkmark'
|
|
212
216
|
},
|
|
@@ -1191,6 +1195,46 @@ export default class Resource {
|
|
|
1191
1195
|
return this._save(...arguments);
|
|
1192
1196
|
}
|
|
1193
1197
|
|
|
1198
|
+
_collectionUrl() {
|
|
1199
|
+
const schema = this.$getters['schemaFor'](this.type);
|
|
1200
|
+
|
|
1201
|
+
if ( !schema ) {
|
|
1202
|
+
// Schema not found - likely due to lack of permissions to view this resource type
|
|
1203
|
+
throw new Error(`${ this.type }: ${ this.t('validation.createResourceFailed', { type: this.typeDisplay }, true) }`);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
let url = schema.linkFor('collection');
|
|
1207
|
+
|
|
1208
|
+
if ( schema.attributes && schema.attributes.namespaced && this.metadata && this.metadata.namespace ) {
|
|
1209
|
+
url += `/${ this.metadata.namespace }`;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
return url;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
async dryRunCreate(data) {
|
|
1216
|
+
try {
|
|
1217
|
+
const url = this._collectionUrl();
|
|
1218
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
1219
|
+
const body = data || this.cleanForSave(this.toSave() || JSON.parse(JSON.stringify(this)), true);
|
|
1220
|
+
|
|
1221
|
+
return this.$dispatch('request', {
|
|
1222
|
+
opt: {
|
|
1223
|
+
method: 'post',
|
|
1224
|
+
url: `${ url }${ separator }dryRun=All`,
|
|
1225
|
+
data: body,
|
|
1226
|
+
headers: {
|
|
1227
|
+
'content-type': 'application/json',
|
|
1228
|
+
accept: 'application/json'
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
type: this.type
|
|
1232
|
+
});
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
return Promise.reject(e);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1194
1238
|
/**
|
|
1195
1239
|
* Remove any unwanted properties from the object that will be saved
|
|
1196
1240
|
*/
|
|
@@ -1209,8 +1253,11 @@ export default class Resource {
|
|
|
1209
1253
|
|
|
1210
1254
|
async _save(opt = { }) {
|
|
1211
1255
|
const forNew = !this.id;
|
|
1256
|
+
let errors;
|
|
1212
1257
|
|
|
1213
|
-
|
|
1258
|
+
if (!opt.skipUIValidation) {
|
|
1259
|
+
errors = this.validationErrors(this, opt);
|
|
1260
|
+
}
|
|
1214
1261
|
|
|
1215
1262
|
if (!isEmpty(errors)) {
|
|
1216
1263
|
return Promise.reject(errors);
|
|
@@ -1219,20 +1266,16 @@ export default class Resource {
|
|
|
1219
1266
|
if ( this.metadata?.resourceVersion ) {
|
|
1220
1267
|
this.metadata.resourceVersion = `${ this.metadata.resourceVersion }`;
|
|
1221
1268
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if ( schema.attributes && schema.attributes.namespaced && this.metadata && this.metadata.namespace ) {
|
|
1229
|
-
url += `/${ this.metadata.namespace }`;
|
|
1269
|
+
try {
|
|
1270
|
+
if ( !opt.url ) {
|
|
1271
|
+
if ( forNew ) {
|
|
1272
|
+
opt.url = this._collectionUrl();
|
|
1273
|
+
} else {
|
|
1274
|
+
opt.url = this.linkFor('update') || this.linkFor('self');
|
|
1230
1275
|
}
|
|
1231
|
-
|
|
1232
|
-
opt.url = url;
|
|
1233
|
-
} else {
|
|
1234
|
-
opt.url = this.linkFor('update') || this.linkFor('self');
|
|
1235
1276
|
}
|
|
1277
|
+
} catch (e) {
|
|
1278
|
+
return Promise.reject(e);
|
|
1236
1279
|
}
|
|
1237
1280
|
|
|
1238
1281
|
if ( !opt.method ) {
|
|
@@ -1254,7 +1297,9 @@ export default class Resource {
|
|
|
1254
1297
|
// @TODO remove this once the API maps steve _type <-> k8s type in both directions
|
|
1255
1298
|
// `JSON.parse(JSON.stringify` - Completely disconnect the object we're going to send and `this`. This ensures that properties
|
|
1256
1299
|
// removed from opt.data before sending (as part of cleanForSave) are not stripped from where they're still needed (`this`)
|
|
1257
|
-
opt.
|
|
1300
|
+
if (!(opt.method === 'patch')) {
|
|
1301
|
+
opt.data = this.toSave() || JSON.parse(JSON.stringify(this));
|
|
1302
|
+
}
|
|
1258
1303
|
|
|
1259
1304
|
if (opt.data._type) {
|
|
1260
1305
|
opt.data.type = opt.data._type;
|
|
@@ -1272,7 +1317,9 @@ export default class Resource {
|
|
|
1272
1317
|
opt.data.annotations = opt.data._annotations;
|
|
1273
1318
|
}
|
|
1274
1319
|
|
|
1275
|
-
|
|
1320
|
+
if (!(opt.method === 'patch')) {
|
|
1321
|
+
opt.data = this.cleanForSave(opt.data, forNew);
|
|
1322
|
+
}
|
|
1276
1323
|
|
|
1277
1324
|
// handle "replace" opt as a query param _replace=true for norman PUT requests
|
|
1278
1325
|
if (opt?.replace && opt.method === 'put') {
|