Merge pull request #3566 from vincent99/master

[Master] Bugs
This commit is contained in:
Vincent Fiduccia 2021-07-29 13:20:17 -07:00 committed by GitHub
commit 2c9c9102bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 255 additions and 186 deletions

View File

@ -307,7 +307,7 @@ authConfig:
5: 'Upload the downloaded JSON file in the OAuth credentials box.' 5: 'Upload the downloaded JSON file in the OAuth credentials box.'
3: 3:
title: 'Create Service Account credentials' title: 'Create Service Account credentials'
introduction: 'Follow <a href="https://rancher.com/docs/rancher/v2.x/en/admin-settings/authentication/google/#creating-service-account-credentials" target="_blank" rel="noopener noreferrer nofollow">this</a> guide to:' introduction: 'Follow <a href="{docsBase}/admin-settings/authentication/google/#creating-service-account-credentials" target="_blank" rel="noopener noreferrer nofollow">this</a> guide to:'
body: body:
1: Create a service account. 1: Create a service account.
2: Generate a key for the service account. 2: Generate a key for the service account.
@ -818,7 +818,7 @@ cis:
alertNeeded: |- alertNeeded: |-
Alerting must be enabled within the CIS chart values.yaml. Alerting must be enabled within the CIS chart values.yaml.
This requires that the <a tabindex="0" href="{link}">{vendor} Monitoring and Alerting app</a> is installed This requires that the <a tabindex="0" href="{link}">{vendor} Monitoring and Alerting app</a> is installed
and the Receivers and Routes are <a target="_blank" rel='noopener noreferrer nofollow' href='https://rancher.com/docs/rancher/v2.x/en/monitoring-alerting/v2.5/configuration/#alertmanager-config'> configured to send out alerts.</a> and the Receivers and Routes are <a target="_blank" rel='noopener noreferrer nofollow' href='{docsBase}/monitoring-alerting/v2.5/configuration/#alertmanager-config'> configured to send out alerts.</a>
alertOnComplete: Alert on scan completion alertOnComplete: Alert on scan completion
alertOnFailure: Alert on scan failure alertOnFailure: Alert on scan failure
benchmarkVersion: Benchmark Version benchmarkVersion: Benchmark Version
@ -2277,7 +2277,7 @@ monitoring:
keyFilePath: keyFilePath:
label: Key File Path label: Key File Path
placeholder: e.g. ./key-file.pfx placeholder: e.g. ./key-file.pfx
secretsBanner: The file paths below must be referenced in <pre class="inline-block m-0 p-0 vertical-middle">alertmanager.alertmanagerSpec.secrets</pre> when deploying the Monitoring chart. For more information see our <a href="https://rancher.com/docs/rancher/v2.5/en/monitoring-alerting/" target="_blank" rel="noopener noreferrer nofollow">documentation</a>. secretsBanner: The file paths below must be referenced in <pre class="inline-block m-0 p-0 vertical-middle">alertmanager.alertmanagerSpec.secrets</pre> when deploying the Monitoring chart. For more information see our <a href="{docsBase}/monitoring-alerting/" target="_blank" rel="noopener noreferrer nofollow">documentation</a>.
route: route:
fields: fields:
@ -2293,7 +2293,7 @@ monitoring:
stepTitle: Uninstall V1 stepTitle: Uninstall V1
stepSubtext: Uninstall Previous Monitoring stepSubtext: Uninstall Previous Monitoring
warning1: V1 Monitoring is currently deployed. This needs to be uninstalled before V2 monitoring can be installed. warning1: V1 Monitoring is currently deployed. This needs to be uninstalled before V2 monitoring can be installed.
warning2: <a target="blank" href="https://rancher.com/docs/rancher/v2.x/en/monitoring-alerting/v2.5/migrating/#migrating-from-monitoring-v1-to-monitoring-v2" target='_blank' rel='noopener nofollow'>Learn more</a> about the migration steps to V2 Monitoring. warning2: <a target="blank" href="{docsBase}/monitoring-alerting/v2.5/migrating/#migrating-from-monitoring-v1-to-monitoring-v2" target='_blank' rel='noopener nofollow'>Learn more</a> about the migration steps to V2 Monitoring.
promptDescription: <div class="mt-20 mb-20">You are attempting to uninstall V1 Monitoring. Please ensure you have read the migration steps.</div> promptDescription: <div class="mt-20 mb-20">You are attempting to uninstall V1 Monitoring. Please ensure you have read the migration steps.</div>
success1: V1 monitoring successfully uninstalled. success1: V1 monitoring successfully uninstalled.
success2: Press Next to continue success2: Press Next to continue
@ -3410,24 +3410,26 @@ servicesPage:
label: Service Type label: Service Type
setup: setup:
currentPassword: Bootstrap Password
confirmPassword: Confirm New Password confirmPassword: Confirm New Password
defaultPasswordError: It looks like this is your first time visiting the Rancher UI; you will need to log in to the local <code>admin</code> account to continue to the setup process.<br><br>If the admin password wasn't preset with an environment variable during installation, one has been randomly generated for you and may be found in your install logs. Alternatively follow the instructions <a rel="noopener noreferrer nofollow" target="_blank" href="https://rancher.com/docs/rancher/v2.5/en/faq/technical/">here</a> to reset the admin password. defaultPassword:
intro: It looks like this is your first time visiting {vendor}; if you pre-set your own bootstrap password, enter it here. Otherwise a random one has been generated for you.<br/><br/>
dockerPrefix: 'For a "docker run" installation:'
dockerCmd: 'docker logs <i>container-id</i> 2&gt;&amp;1 | grep "Bootstrap Password:"'
dockerPs: 'Find your container ID with <code>docker ps</code>, then run:'
dockerSuffix: ""
helmPrefix: 'For a Helm installation, run:'
helmCmd: "kubectl get secret --namespace cattle-system bootstrap-secret -o go-template='{{.data.bootstrapPassword|base64decode}}{{\"\\n\"}}'"
helmSuffix: ""
eula: I agree to the <a href="https://rancher.com/eula" target="_blank" rel="noopener noreferrer nofollow">terms and conditions</a> for using Rancher. eula: I agree to the <a href="https://rancher.com/eula" target="_blank" rel="noopener noreferrer nofollow">terms and conditions</a> for using Rancher.
newPassword: New Password newPassword: New Password
newUserSetPassword: The first order of business is to set a strong password. We suggest using this random one generated just for you, but enter your own if you like. newUserSetPassword: The first order of business is to set a strong password. We suggest using this random one generated just for you, but enter your own if you like.
serverUrl: serverUrl:
label: Server URL label: Server URL
skip: Skip skip: Skip
tip: What URL should be used for this Rancher installation? All the nodes in your clusters will need to be able to reach this. You can skip setting this for now, and update it later in General Settings>Advanced Settings. tip: What URL should be used for this {vendor} installation? All the nodes in your clusters will need to be able to reach this.
setPassword: The first order of business is to set a strong password for the default <code>{username}</code> user. We suggest using this random one generated just for you, but enter your own if you like. setPassword: The first order of business is to set a strong password for the default <code>{username}</code> user. We suggest using this random one generated just for you, but enter your own if you like.
telemetry: telemetry: Allow collection of <a href="{docsBase}/faq/telemetry/" target="_blank" rel="noopener noreferrer nofollow">anonymous statistics</a> to help us improve {vendor}.
label: Allow collection of anonymous statistics to help us improve Rancher
tip: 'Rancher Labs would like to collect a bit of anonymized information
about the configuration of your installation to help make Rancher better.
Your data will not be shared with anyone else, and no information about
what specific resources or endpoints you are deploying is included.
Once enabled you can view exactly what data will be sent at <code>/v1-telemetry</code>.
<a href="https://rancher.com/docs/rancher/v2.x/en/faq/telemetry/" target="_blank" rel="noopener noreferrer nofollow">More Info</a>'
useManual: Set a specific password to use useManual: Set a specific password to use
useRandom: Use a randomly generated password useRandom: Use a randomly generated password
welcome: Welcome to {vendor}! welcome: Welcome to {vendor}!
@ -4997,13 +4999,13 @@ embedding:
v1ClusterTools: v1ClusterTools:
monitoring: monitoring:
label: Monitoring (Legacy) label: Monitoring (Legacy)
description: 'Legacy V1 monitoring. V1 Monitoring is deprecated since Rancher 2.5.0. <a target="blank" href="https://rancher.com/docs/rancher/v2.x/en/monitoring-alerting/v2.5/migrating/#migrating-from-monitoring-v1-to-monitoring-v2">Learn more</a> about the migration steps to V2 Monitoring.' description: 'Legacy V1 monitoring. V1 Monitoring is deprecated since Rancher 2.5.0. <a target="blank" href="{docsBase}/monitoring-alerting/v2.5/migrating/#migrating-from-monitoring-v1-to-monitoring-v2">Learn more</a> about the migration steps to V2 Monitoring.'
logging: logging:
label: Logging (Legacy) label: Logging (Legacy)
description: 'Legacy V1 logging. V1 Logging is deprecated since Rancher 2.5.0. <a target="blank" href="https://rancher.com/docs/rancher/v2.x/en/logging/v2.5/migrating/">Learn more</a> about migrating to V2 Logging.' description: 'Legacy V1 logging. V1 Logging is deprecated since Rancher 2.5.0. <a target="blank" href="{docsBase}/logging/v2.5/migrating/">Learn more</a> about migrating to V2 Logging.'
istio: istio:
label: Istio (Legacy) label: Istio (Legacy)
description: 'Legacy V1 Istio. Istio v1.5 has been deprecated since Rancher 2.5.0. <a target="blank" href="https://rancher.com/docs/rancher/v2.5/en/istio/#migrate-from-previous-istio-version">Learn more</a> about migrating to the latest version.' description: 'Legacy V1 Istio. Istio v1.5 has been deprecated since Rancher 2.5.0. <a target="blank" href="{docsBase}/istio/#migrate-from-previous-istio-version">Learn more</a> about migrating to the latest version.'
legacy: legacy:
alerts: Alerts alerts: Alerts

View File

@ -9,6 +9,10 @@ export default {
mixins: [CreateEditView], mixins: [CreateEditView],
data() { data() {
if ( !this.value.decodedData.environment ) {
this.value.setData('environment', 'AzurePublicCloud');
}
return { azureEnvironments }; return { azureEnvironments };
}, },
@ -22,9 +26,6 @@ export default {
'value.decodedData.subscriptionId'(neu) { 'value.decodedData.subscriptionId'(neu) {
this.$emit('validationChanged', !!neu); this.$emit('validationChanged', !!neu);
}, },
'value.decodedData.tenantId'(neu) {
this.$emit('validationChanged', !!neu);
},
'value.decodedData.environment'(neu) { 'value.decodedData.environment'(neu) {
this.$emit('validationChanged', !!neu); this.$emit('validationChanged', !!neu);
}, },
@ -36,7 +37,6 @@ export default {
clientId, clientId,
clientSecret, clientSecret,
subscriptionId, subscriptionId,
tenantId,
} = this.value.decodedData; } = this.value.decodedData;
try { try {
@ -47,7 +47,6 @@ export default {
clientId, clientId,
clientSecret, clientSecret,
subscriptionId, subscriptionId,
tenantId,
}, },
}); });
@ -62,46 +61,6 @@ export default {
<template> <template>
<section> <section>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput
:value="value.decodedData.tenantId"
label-key="cluster.credential.azure.tenantId.label"
type="text"
:mode="mode"
@input="value.setData('tenantId', $event)"
/>
</div>
<div class="col span-6">
<LabeledInput
:value="value.decodedData.subscriptionId"
label-key="cluster.credential.azure.subscriptionId.label"
type="text"
:mode="mode"
@input="value.setData('subscriptionId', $event)"
/>
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput
:value="value.decodedData.clientId"
label-key="cluster.credential.azure.clientId.label"
type="text"
:mode="mode"
@input="value.setData('clientId', $event)"
/>
</div>
<div class="col span-6">
<LabeledInput
:value="value.decodedData.clientSecret"
label-key="cluster.credential.azure.clientSecret.label"
type="password"
:mode="mode"
@input="value.setData('clientSecret', $event)"
/>
</div>
</div>
<div class="row mb-10"> <div class="row mb-10">
<div class="col span-6"> <div class="col span-6">
<LabeledSelect <LabeledSelect
@ -116,6 +75,38 @@ export default {
@input="value.setData('environment', $event)" @input="value.setData('environment', $event)"
/> />
</div> </div>
<div class="col span-6">
<LabeledInput
:value="value.decodedData.subscriptionId"
label-key="cluster.credential.azure.subscriptionId.label"
type="text"
:mode="mode"
:required="true"
@input="value.setData('subscriptionId', $event)"
/>
</div>
</div>
<div class="row mb-10">
<div class="col span-6">
<LabeledInput
:value="value.decodedData.clientId"
label-key="cluster.credential.azure.clientId.label"
type="text"
:mode="mode"
:required="true"
@input="value.setData('clientId', $event)"
/>
</div>
<div class="col span-6">
<LabeledInput
:value="value.decodedData.clientSecret"
label-key="cluster.credential.azure.clientSecret.label"
type="password"
:mode="mode"
:required="true"
@input="value.setData('clientSecret', $event)"
/>
</div>
</div> </div>
</section> </section>
</template> </template>

View File

@ -1,4 +1,16 @@
<script> <script>
import { isArray } from '@/utils/array';
function flatten(node) {
if ( isArray(node) ) {
return node.map(flatten).join(' ');
} else if ( node.children ) {
return node.children.map(flatten).join(' ');
} else if ( node.text ) {
return node.text;
}
}
export default { export default {
data() { data() {
return { copied: false }; return { copied: false };
@ -9,7 +21,9 @@ export default {
$event.stopPropagation(); $event.stopPropagation();
$event.preventDefault(); $event.preventDefault();
this.$copyText(this.$slots.default[0].text.trim()).then(() => { const content = flatten(this.$slots.default).trim();
this.$copyText(content).then(() => {
this.copied = true; this.copied = true;
setTimeout(() => { setTimeout(() => {

View File

@ -72,28 +72,39 @@ export default {
methods: { methods: {
clicked(event) { clicked(event) {
if (!this.isDisabled) { if ( event.target.tagName === 'A' && event.target.href ) {
const click = $.Event('click'); // Ignore links inside the checkbox label so you can click them
return true;
}
click.shiftKey = event.shiftKey; event.stopPropagation();
click.altKey = event.altKey; event.preventDefault();
click.ctrlKey = event.ctrlKey;
click.metaKey = event.metaKey;
// Flip the value if (this.isDisabled) {
if (this.isMulti()) { return;
if (this.isChecked) { }
removeObject(this.value, this.valueWhenTrue);
} else { const click = $.Event('click');
addObject(this.value, this.valueWhenTrue);
} click.shiftKey = event.shiftKey;
this.$emit('input', this.value); click.altKey = event.altKey;
click.ctrlKey = event.ctrlKey;
click.metaKey = event.metaKey;
// Flip the value
if (this.isMulti()) {
if (this.isChecked) {
removeObject(this.value, this.valueWhenTrue);
} else { } else {
this.$emit('input', !this.value); addObject(this.value, this.valueWhenTrue);
$(this.$el).trigger(click);
} }
this.$emit('input', this.value);
} else {
this.$emit('input', !this.value);
$(this.$el).trigger(click);
} }
}, },
isMulti() { isMulti() {
return Array.isArray(this.value); return Array.isArray(this.value);
} }
@ -108,7 +119,7 @@ export default {
:class="{ 'disabled': isDisabled}" :class="{ 'disabled': isDisabled}"
@keydown.enter.prevent="clicked($event)" @keydown.enter.prevent="clicked($event)"
@keydown.space.prevent="clicked($event)" @keydown.space.prevent="clicked($event)"
@click.stop.prevent="clicked($event)" @click="clicked($event)"
> >
<input <input
v-model="value" v-model="value"

View File

@ -123,6 +123,10 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
valueTrim: {
type: Boolean,
default: true,
},
valueBase64: { valueBase64: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -373,7 +377,11 @@ export default {
if (value && typeOf(value) === 'object') { if (value && typeOf(value) === 'object') {
out[key] = JSON.parse(JSON.stringify(value)); out[key] = JSON.parse(JSON.stringify(value));
} else { } else {
value = (value || '').trim(); value = value || '';
if ( this.valueTrim ) {
value = value.trim();
}
if ( value && this.valueBase64 ) { if ( value && this.valueBase64 ) {
value = base64Encode(value); value = base64Encode(value);

View File

@ -99,7 +99,7 @@ export const fetchOrCreateSetting = async(store, id, val, save = true) => {
}); });
if ( save ) { if ( save ) {
setting.save({ url }); await setting.save({ url });
} }
} }
@ -111,5 +111,7 @@ export const setSetting = async(store, id, val) => {
setting.value = val; setting.value = val;
return setting.save(); await setting.save();
return setting;
}; };

View File

@ -53,6 +53,7 @@ export default {
:mode="mode" :mode="mode"
:initial-empty-row="true" :initial-empty-row="true"
:value-base64="true" :value-base64="true"
:value-trim="false"
:value-concealed="isView && hideSensitiveData" :value-concealed="isView && hideSensitiveData"
:file-modifier="fileModifier" :file-modifier="fileModifier"
read-icon="" read-icon=""

View File

@ -345,6 +345,16 @@ export default {
this.$set(this.value, 'tags', ary.join(',')); this.$set(this.value, 'tags', ary.join(','));
}, },
test() {
const errors = [];
if (!this.selectedNetwork) {
errors.push(this.t('validation.required', { key: 'VPC/Subnet' }, true));
}
return { errors };
},
}, },
}; };
</script> </script>

View File

@ -6,7 +6,7 @@ import { createCssVars } from '@/utils/color';
export default { export default {
async fetch() { async fetch() {
this.globalSettings = await this.$store.dispatch('management/findAll', { type: MANAGEMENT.SETTING }); this.globalSettings = await this.$store.dispatch('management/findAll', { type: MANAGEMENT.SETTING, opt: { url: `/v1/${ MANAGEMENT.SETTING }` } });
}, },
data() { data() {

View File

@ -30,6 +30,10 @@ export default {
return out; return out;
}, },
canClone() {
return false;
},
openSsh() { openSsh() {
return () => { return () => {
this.$dispatch('wm/open', { this.$dispatch('wm/open', {

View File

@ -383,6 +383,10 @@ export default {
return true; return true;
}, },
canClone() {
return false;
},
// You need to preload CAPI.MACHINEs to use this // You need to preload CAPI.MACHINEs to use this
provisionedMachine() { provisionedMachine() {
const namespace = this.metadata?.annotations?.[CAPI_ANNOTATIONS.CLUSTER_NAMESPACE]; const namespace = this.metadata?.annotations?.[CAPI_ANNOTATIONS.CLUSTER_NAMESPACE];

View File

@ -85,6 +85,9 @@ export default {
id: this.namespace id: this.namespace
} }
}; };
} },
canClone() {
return false;
},
}; };

View File

@ -61,7 +61,7 @@ export default {
label: 'Download KubeConfig', label: 'Download KubeConfig',
icon: 'icon icon-download', icon: 'icon icon-download',
bulkable: true, bulkable: true,
enabled: this.$rootGetters['isRancher'], enabled: this.$rootGetters['isRancher'] && this.mgmt?.isReady,
}); });
insertAt(out, idx++, { insertAt(out, idx++, {

View File

@ -4,6 +4,8 @@ import { USERNAME } from '@/config/cookies';
import LabeledInput from '@/components/form/LabeledInput'; import LabeledInput from '@/components/form/LabeledInput';
import AsyncButton from '@/components/AsyncButton'; import AsyncButton from '@/components/AsyncButton';
import BrandImage from '@/components/BrandImage'; import BrandImage from '@/components/BrandImage';
import InfoBox from '@/components/InfoBox';
import CopyCode from '@/components/CopyCode';
import Banner from '@/components/Banner'; import Banner from '@/components/Banner';
import { LOCAL, LOGGED_OUT, TIMED_OUT, _FLAGGED } from '@/config/query-params'; import { LOCAL, LOGGED_OUT, TIMED_OUT, _FLAGGED } from '@/config/query-params';
import Checkbox from '@/components/form/Checkbox'; import Checkbox from '@/components/form/Checkbox';
@ -15,13 +17,13 @@ import { _ALL_IF_AUTHED } from '@/plugins/steve/actions';
import { MANAGEMENT, NORMAN } from '@/config/types'; import { MANAGEMENT, NORMAN } from '@/config/types';
import { SETTING } from '@/config/settings'; import { SETTING } from '@/config/settings';
import { LOGIN_ERRORS } from '@/store/auth'; import { LOGIN_ERRORS } from '@/store/auth';
import { getVendor, getProduct, setVendor } from '../../config/private-label'; import { getVendor, getProduct, setVendor } from '@/config/private-label';
export default { export default {
name: 'Login', name: 'Login',
layout: 'unauthenticated', layout: 'unauthenticated',
components: { components: {
LabeledInput, AsyncButton, Checkbox, BrandImage, Banner LabeledInput, AsyncButton, Checkbox, BrandImage, Banner, InfoBox, CopyCode
}, },
async asyncData({ route, redirect, store }) { async asyncData({ route, redirect, store }) {
@ -229,8 +231,8 @@ export default {
<template> <template>
<main class="login"> <main class="login">
<div class="row mb-20"> <div class="row gutless mb-20">
<div class="col span-6"> <div class="col span-6 p-20">
<p class="text-center"> <p class="text-center">
{{ t('login.howdy') }} {{ t('login.howdy') }}
</p> </p>
@ -249,9 +251,31 @@ export default {
</h4> </h4>
</div> </div>
<div v-if="firstLogin" class="first-login-message"> <div v-if="firstLogin" class="first-login-message">
<Banner color="info"> <InfoBox color="info">
<t k="setup.defaultPasswordError" :raw="true" /> <t k="setup.defaultPassword.intro" :raw="true" />
</Banner>
<div><t k="setup.defaultPassword.dockerPrefix" :raw="true" /></div>
<ul>
<li>
<t k="setup.defaultPassword.dockerPs" :raw="true" />
</li>
<li>
<CopyCode>
<t k="setup.defaultPassword.dockerCmd" :raw="true" />
</CopyCode>
</li>
</ul>
<div><t k="setup.defaultPassword.dockerSuffix" :raw="true" /></div>
<br />
<div><t k="setup.defaultPassword.helmPrefix" :raw="true" /></div>
<br />
<CopyCode>
<t k="setup.defaultPassword.helmCmd" :raw="true" />
</CopyCode>
<br />
<div><t k="setup.defaultPassword.helmSuffix" :raw="true" /></div>
</InfoBox>
</div> </div>
<div v-if="(!hasLocal || (hasLocal && !showLocal)) && providers.length" class="mt-30"> <div v-if="(!hasLocal || (hasLocal && !showLocal)) && providers.length" class="mt-30">

View File

@ -23,7 +23,9 @@ const calcIsFirstLogin = (store) => {
const calcMustChangePassword = async(store) => { const calcMustChangePassword = async(store) => {
await store.dispatch('auth/getUser'); await store.dispatch('auth/getUser');
return store.getters['auth/v3User']?.mustChangePassword; const out = store.getters['auth/v3User']?.mustChangePassword;
return out;
}; };
export default { export default {
@ -40,8 +42,8 @@ export default {
} catch (e) { } catch (e) {
} }
const isFirstLogin = calcIsFirstLogin(store); const isFirstLogin = await calcIsFirstLogin(store);
const mustChangePassword = calcMustChangePassword(store); const mustChangePassword = await calcMustChangePassword(store);
if (isFirstLogin) { if (isFirstLogin) {
// Always show setup if this is the first log in // Always show setup if this is the first log in
@ -81,7 +83,7 @@ export default {
const principals = await store.dispatch('rancher/findAll', { type: NORMAN.PRINCIPAL, opt: { url: '/v3/principals' } }); const principals = await store.dispatch('rancher/findAll', { type: NORMAN.PRINCIPAL, opt: { url: '/v3/principals' } });
const me = findBy(principals, 'me', true); const me = findBy(principals, 'me', true);
const current = route.query[SETUP] || store.getters['auth/initialPass'] || 'admin'; const current = route.query[SETUP] || store.getters['auth/initialPass'];
const v3User = store.getters['auth/v3User'] ?? {}; const v3User = store.getters['auth/v3User'] ?? {};
const mcmFeature = await store.dispatch('management/find', { const mcmFeature = await store.dispatch('management/find', {
@ -100,8 +102,8 @@ export default {
serverUrl = window.location.origin; serverUrl = window.location.origin;
} }
const isFirstLogin = calcIsFirstLogin(store); const isFirstLogin = await calcIsFirstLogin(store);
const mustChangePassword = calcMustChangePassword(store); const mustChangePassword = await calcMustChangePassword(store);
return { return {
vendor: getVendor(), vendor: getVendor(),
@ -111,8 +113,8 @@ export default {
useRandom: true, useRandom: true,
haveCurrent: !!current, haveCurrent: !!current,
username: me?.loginName || 'admin', username: me?.loginName || 'admin',
mustSetup: isFirstLogin, isFirstLogin,
mustChangePassword: isFirstLogin || mustChangePassword, mustChangePassword,
current, current,
password: randomStr(), password: randomStr(),
confirm: '', confirm: '',
@ -132,24 +134,24 @@ export default {
}, },
computed: { computed: {
passwordSubmitDisabled() { saveEnabled() {
if (!this.eula && this.mustSetup) { if ( !this.eula ) {
return true;
}
if ( this.useRandom ) {
return false; return false;
} }
if ( !this.password || this.password !== this.confirm ) { if ( this.mustChangePassword ) {
return true; if ( !this.current ) {
return false;
}
if ( !this.useRandom ) {
if ( !this.password || this.password !== this.confirm ) {
return false;
}
}
} }
if ( !this.current ) { return true;
return true;
}
return false;
}, },
me() { me() {
@ -174,64 +176,56 @@ export default {
}, },
mounted() { mounted() {
this.$refs.password.focus(); const el = this.$refs.password;
this.$refs.password.select();
if ( el ) {
el.focus();
el.select();
}
}, },
methods: { methods: {
async finishPassword(buttonCb) { async save(buttonCb) {
const promises = [];
try { try {
if (this.mustSetup) { await this.$store.dispatch('loadManagement');
await this.$store.dispatch('loadManagement');
await Promise.all([ if ( this.mustChangePassword ) {
setSetting(this.$store, SETTING.EULA_AGREED, (new Date()).toISOString() ), await this.$store.dispatch('rancher/request', {
setSetting(this.$store, SETTING.TELEMETRY, this.telemetry ? 'in' : 'out'), url: '/v3/users?action=changepassword',
setSetting(this.$store, SETTING.FIRST_LOGIN, 'false'), method: 'post',
]); data: {
currentPassword: this.current,
newPassword: this.password
},
});
} else {
promises.push(setSetting(this.$store, SETTING.FIRST_LOGIN, 'false'));
} }
await this.$store.dispatch('rancher/request', {
url: '/v3/users?action=changepassword',
method: 'post',
data: {
currentPassword: this.current,
newPassword: this.password
},
});
const user = this.v3User; const user = this.v3User;
user.mustChangePassword = false; user.mustChangePassword = false;
this.$store.dispatch('auth/gotUser', user); this.$store.dispatch('auth/gotUser', user);
if (!this.mustSetup && this.mustChangePassword) { promises.push( setSetting(this.$store, SETTING.EULA_AGREED, (new Date()).toISOString()) );
buttonCb(true); promises.push( setSetting(this.$store, SETTING.TELEMETRY, this.telemetry ? 'in' : 'out') );
this.done();
} else { if ( this.mcmEnabled && this.serverUrl ) {
if (this.mcmEnabled) { promises.push( setSetting(this.$store, SETTING.SERVER_URL, this.serverUrl) );
this.step = 2;
} else {
this.done();
}
buttonCb(true);
} }
await Promise.all(promises);
buttonCb(true);
this.done();
} catch (err) { } catch (err) {
buttonCb(false); buttonCb(false);
this.errors = exceptionToErrorsArray(err); this.errors = exceptionToErrorsArray(err);
} }
}, },
async setServerUrl(buttonCb) {
try {
await setSetting(this.$store, SETTING.SERVER_URL, this.serverUrl);
buttonCb(true);
this.done();
} catch {
buttonCb(false);
}
},
done() { done() {
this.$router.replace('/'); this.$router.replace('/');
}, },
@ -240,7 +234,7 @@ export default {
</script> </script>
<template> <template>
<div class="setup"> <form class="setup">
<div class="row"> <div class="row">
<div class="col span-6 form-col"> <div class="col span-6 form-col">
<div> <div>
@ -251,12 +245,21 @@ export default {
{{ t('setup.welcome', {product}) }} {{ t('setup.welcome', {product}) }}
</h1> </h1>
<template v-if="step===1"> <template v-if="mustChangePassword">
<p <p
class="text-center mb-40 mt-20 setup-title" class="text-center mb-20 mt-20 setup-title"
v-html="t(mustSetup ? 'setup.setPassword' : 'setup.newUserSetPassword', { username }, true)" v-html="t(isFirstLogin ? 'setup.setPassword' : 'setup.newUserSetPassword', { username }, true)"
></p> ></p>
<LabeledInput
v-if="!haveCurrent"
v-model.trim="current"
autocomplete="current-password"
type="password"
label-key="setup.currentPassword"
class="mb-20"
/>
<!-- For password managers... --> <!-- For password managers... -->
<input type="hidden" name="username" autocomplete="username" :value="username" /> <input type="hidden" name="username" autocomplete="username" :value="username" />
<div class="mb-20"> <div class="mb-20">
@ -284,38 +287,29 @@ export default {
type="password" type="password"
label-key="setup.confirmPassword" label-key="setup.confirmPassword"
/> />
<div v-if="mustSetup">
<div class="checkbox mt-40">
<Checkbox v-model="telemetry" :label="t('setup.telemetry.label')" type="checkbox" />
<i v-tooltip="{content:t('setup.telemetry.tip', {}, true), delay: {hide:500}, autoHide: false}" class="icon icon-info" />
</div>
<div class="checkbox pt-10 eula">
<Checkbox v-model="eula" type="checkbox" />
<span v-html="t('setup.eula', {}, true)"></span>
</div>
</div>
<div class="text-center mt-20">
<AsyncButton key="passwordSubmit" type="submit" mode="continue" :disabled="passwordSubmitDisabled" @click="finishPassword" />
</div>
</template> </template>
<template v-else> <template v-if="mcmEnabled">
<hr v-if="mustChangePassword" class="mt-20 mb-20" />
<p> <p>
<t k="setup.serverUrl.tip" :raw="true" /> <t k="setup.serverUrl.tip" :raw="true" />
</p> </p>
<div class="mt-20"> <div class="mt-20">
<LabeledInput v-model="serverUrl" :label="t('setup.serverUrl.label')" /> <LabeledInput v-model="serverUrl" :label="t('setup.serverUrl.label')" />
</div> </div>
<div class="text-center mt-20">
<button type="button" class="btn role-link" @click="done">
{{ t('setup.serverUrl.skip') }}
</button>
<AsyncButton type="submit" mode="continue" @click="setServerUrl" />
</div>
</template> </template>
<div class="checkbox mt-40">
<Checkbox v-model="telemetry" label-key="setup.telemetry" />
</div>
<div class="checkbox pt-10 eula">
<Checkbox v-model="eula" label-key="setup.eula" />
</div>
<div class="text-center mt-20">
<AsyncButton key="passwordSubmit" type="submit" mode="continue" :disabled="!saveEnabled" @click="save" />
</div>
<div class="setup-errors mt-20"> <div class="setup-errors mt-20">
<h4 v-for="err in errors" :key="err" class="text-error text-center"> <h4 v-for="err in errors" :key="err" class="text-error text-center">
{{ err }} {{ err }}
@ -326,7 +320,7 @@ export default {
<div class="col span-6 landscape" /> <div class="col span-6 landscape" />
</div> </div>
</div> </form>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -122,7 +122,7 @@ export default {
try { try {
ns = await this.$store.dispatch('cluster/find', { type: NAMESPACE, id: this.forceNamespace }); ns = await this.$store.dispatch('cluster/find', { type: NAMESPACE, id: this.forceNamespace });
const project = ns.metadata.annotations[PROJECT]; const project = ns.metadata.annotations?.[PROJECT];
if (project) { if (project) {
this.project = project.replace(':', '/'); this.project = project.replace(':', '/');
@ -492,7 +492,7 @@ export default {
if (neu) { if (neu) {
const ns = this.$store.getters['cluster/byId'](NAMESPACE, this.value.metadata.namespace); const ns = this.$store.getters['cluster/byId'](NAMESPACE, this.value.metadata.namespace);
const project = ns?.metadata.annotations[PROJECT]; const project = ns?.metadata.annotations?.[PROJECT];
if (project) { if (project) {
this.project = project.replace(':', '/'); this.project = project.replace(':', '/');

View File

@ -70,7 +70,7 @@ export const getters = {
msg = get(state.translations[state.default], key); msg = get(state.translations[state.default], key);
} }
if ( !msg ) { if ( msg === undefined ) {
return undefined; return undefined;
} }
@ -94,8 +94,9 @@ export const getters = {
} else if ( formatter && formatter.format ) { } else if ( formatter && formatter.format ) {
// Inject things like appName so they're always available in any translation // Inject things like appName so they're always available in any translation
const moreArgs = { const moreArgs = {
vendor: getVendor(), vendor: getVendor(),
appName: getProduct(), appName: getProduct(),
docsBase: 'https://rancher.com/docs/rancher/v2.6/en',
...args ...args
}; };

View File

@ -95,7 +95,7 @@ export const HIDE_HOME_PAGE_CARDS = create('home-page-cards', {}, { parseJSON }
export const _RKE1 = 'rke1'; export const _RKE1 = 'rke1';
export const _RKE2 = 'rke2'; export const _RKE2 = 'rke2';
export const PROVISIONER = create('provisioner', _RKE2, { options: [_RKE1, _RKE2] }); export const PROVISIONER = create('provisioner', _RKE1, { options: [_RKE1, _RKE2] });
// Promo for Cluster Tools feature on Cluster Dashboard page // Promo for Cluster Tools feature on Cluster Dashboard page
export const CLUSTER_TOOLS_TIP = create('hide-cluster-tools-tip', false, { parseJSON }); export const CLUSTER_TOOLS_TIP = create('hide-cluster-tools-tip', false, { parseJSON });