mirror of https://github.com/apache/cloudstack.git
Fix local upload from browser failing due to ssvm cert not trusted (#13204)
This commit is contained in:
parent
c267ad39bc
commit
ff9ee24d12
|
|
@ -1617,6 +1617,8 @@
|
|||
"label.offeringid": "Offering ID",
|
||||
"label.offeringtype": "Compute Offering type",
|
||||
"label.ok": "OK",
|
||||
"label.ssvm.open.cert.page": "Open Certificate Page",
|
||||
"label.retry.upload": "Retry Upload",
|
||||
"label.only.end.date.and.time": "Only end date and time",
|
||||
"label.only.start.date.and.time": "Only start date and time",
|
||||
"label.open.documentation": "Open documentation",
|
||||
|
|
@ -3667,6 +3669,9 @@
|
|||
"message.upload.iso.failed.description": "Failed to upload ISO.",
|
||||
"message.upload.template.failed.description": "Failed to upload Template",
|
||||
"message.upload.volume.failed": "Volume upload failed",
|
||||
"message.ssvm.cert.untrusted": "Unable to reach the upload server.",
|
||||
"message.ssvm.cert.trust.instructions": "The upload server may be using a self-signed or untrusted certificate. Click 'Open Certificate Page' to open the server in a new browser tab, accept the certificate warning, then return here and click 'Retry Upload'. If the server remains unreachable, contact your administrator.",
|
||||
"message.ssvm.unreachable.retry": "The upload server is still unreachable. If it uses a self-signed certificate, please accept it in the opened tab and try again.",
|
||||
"message.user.not.permitted.api": "User is not permitted to use the API",
|
||||
"message.validate.equalto": "Please enter the same value again.",
|
||||
"message.validate.max": "Please enter a value less than or equal to {0}.",
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ a {
|
|||
text-align: right;
|
||||
padding-top: 15px;
|
||||
|
||||
button {
|
||||
button, a.ant-btn {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
const SSVM_PROBE_TIMEOUT_MS = 5000
|
||||
export async function probeSsvmCert (origin) {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), SSVM_PROBE_TIMEOUT_MS)
|
||||
try {
|
||||
await fetch(origin, { method: 'HEAD', mode: 'no-cors', signal: controller.signal })
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,11 +19,27 @@
|
|||
<div
|
||||
class="form-layout"
|
||||
@keyup.ctrl.enter="handleSubmit">
|
||||
<span v-if="uploadPercentage > 0">
|
||||
<span v-if="uploading">
|
||||
<loading-outlined />
|
||||
{{ $t('message.upload.file.processing') }}
|
||||
<a-progress :percent="uploadPercentage" />
|
||||
</span>
|
||||
<div v-else-if="ssvmCertUntrusted" class="ssvm-cert-warning">
|
||||
<a-alert
|
||||
type="warning"
|
||||
show-icon
|
||||
:message="$t('message.ssvm.cert.untrusted')"
|
||||
:description="$t('message.ssvm.cert.trust.instructions')" />
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :href="ssvmOrigin" target="_blank" rel="noopener noreferrer">
|
||||
{{ $t('label.ssvm.open.cert.page') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="retryUpload">
|
||||
{{ $t('label.retry.upload') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-spin :spinning="loading" v-else>
|
||||
<a-form
|
||||
:ref="formRef"
|
||||
|
|
@ -311,6 +327,7 @@ import { api } from '@/api'
|
|||
import store from '@/store'
|
||||
import { axios } from '../../utils/request'
|
||||
import { mixinForm } from '@/utils/mixin'
|
||||
import { probeSsvmCert } from '@/utils/ssvmProbe'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
|
||||
|
|
@ -343,9 +360,12 @@ export default {
|
|||
userdatapolicy: null,
|
||||
userdatapolicylist: {},
|
||||
loading: false,
|
||||
uploading: false,
|
||||
allowed: false,
|
||||
uploadParams: null,
|
||||
uploadPercentage: 0,
|
||||
ssvmCertUntrusted: false,
|
||||
ssvmOrigin: '',
|
||||
currentForm: ['plus-outlined', 'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload',
|
||||
domains: [],
|
||||
accounts: [],
|
||||
|
|
@ -489,6 +509,17 @@ export default {
|
|||
this.form.file = file
|
||||
return false
|
||||
},
|
||||
async retryUpload () {
|
||||
this.loading = true
|
||||
const reachable = await probeSsvmCert(this.ssvmOrigin)
|
||||
this.loading = false
|
||||
if (!reachable) {
|
||||
this.$message.warning(this.$t('message.ssvm.unreachable.retry'))
|
||||
return
|
||||
}
|
||||
this.ssvmCertUntrusted = false
|
||||
this.handleUpload()
|
||||
},
|
||||
handleUpload () {
|
||||
const { fileList } = this
|
||||
if (this.fileList.length > 1) {
|
||||
|
|
@ -502,6 +533,7 @@ export default {
|
|||
fileList.forEach(file => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploading = true
|
||||
this.uploadPercentage = 0
|
||||
axios.post(this.uploadParams.postURL,
|
||||
formData,
|
||||
|
|
@ -529,6 +561,8 @@ export default {
|
|||
description: `${this.$t('message.upload.iso.failed.description')} - ${e}`,
|
||||
duration: 0
|
||||
})
|
||||
}).finally(() => {
|
||||
this.uploading = false
|
||||
})
|
||||
},
|
||||
handleSubmit (e) {
|
||||
|
|
@ -583,18 +617,18 @@ export default {
|
|||
}
|
||||
params.format = 'ISO'
|
||||
this.loading = true
|
||||
api('getUploadParamsForIso', params).then(json => {
|
||||
api('getUploadParamsForIso', params).then(async json => {
|
||||
this.uploadParams = (json.postuploadisoresponse && json.postuploadisoresponse.getuploadparams) ? json.postuploadisoresponse.getuploadparams : ''
|
||||
const response = this.handleUpload()
|
||||
if (this.userdataid !== null) {
|
||||
this.linkUserdataToTemplate(this.userdataid, json.postuploadisoresponse.iso[0].id)
|
||||
}
|
||||
if (response === 'upload successful') {
|
||||
this.$notification.success({
|
||||
message: this.$t('message.success.upload'),
|
||||
description: this.$t('message.success.upload.iso.description')
|
||||
})
|
||||
this.ssvmOrigin = new URL(this.uploadParams.postURL).origin
|
||||
const trusted = await probeSsvmCert(this.ssvmOrigin)
|
||||
if (!trusted) {
|
||||
this.ssvmCertUntrusted = true
|
||||
return
|
||||
}
|
||||
this.handleUpload()
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
|
|
|
|||
|
|
@ -19,11 +19,27 @@
|
|||
<div
|
||||
:class="'form-layout'"
|
||||
@keyup.ctrl.enter="handleSubmit">
|
||||
<span v-if="uploadPercentage > 0">
|
||||
<span v-if="uploading">
|
||||
<loading-outlined />
|
||||
{{ $t('message.upload.file.processing') }}
|
||||
<a-progress :percent="uploadPercentage" />
|
||||
</span>
|
||||
<div v-else-if="ssvmCertUntrusted" class="ssvm-cert-warning">
|
||||
<a-alert
|
||||
type="warning"
|
||||
show-icon
|
||||
:message="$t('message.ssvm.cert.untrusted')"
|
||||
:description="$t('message.ssvm.cert.trust.instructions')" />
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :href="ssvmOrigin" target="_blank" rel="noopener noreferrer">
|
||||
{{ $t('label.ssvm.open.cert.page') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="retryUpload">
|
||||
{{ $t('label.retry.upload') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-spin :spinning="loading" v-else>
|
||||
<a-form
|
||||
:ref="formRef"
|
||||
|
|
@ -472,6 +488,7 @@ import { api } from '@/api'
|
|||
import store from '@/store'
|
||||
import { axios } from '../../utils/request'
|
||||
import { mixinForm } from '@/utils/mixin'
|
||||
import { probeSsvmCert } from '@/utils/ssvmProbe'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
|
||||
|
|
@ -497,6 +514,8 @@ export default {
|
|||
uploadPercentage: 0,
|
||||
uploading: false,
|
||||
fileList: [],
|
||||
ssvmCertUntrusted: false,
|
||||
ssvmOrigin: '',
|
||||
zones: {},
|
||||
defaultZone: '',
|
||||
hyperVisor: {},
|
||||
|
|
@ -610,12 +629,24 @@ export default {
|
|||
this.form.file = file
|
||||
return false
|
||||
},
|
||||
async retryUpload () {
|
||||
this.loading = true
|
||||
const reachable = await probeSsvmCert(this.ssvmOrigin)
|
||||
this.loading = false
|
||||
if (!reachable) {
|
||||
this.$message.warning(this.$t('message.ssvm.unreachable.retry'))
|
||||
return
|
||||
}
|
||||
this.ssvmCertUntrusted = false
|
||||
this.handleUpload()
|
||||
},
|
||||
handleUpload () {
|
||||
const { fileList } = this
|
||||
const formData = new FormData()
|
||||
fileList.forEach(file => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploading = true
|
||||
this.uploadPercentage = 0
|
||||
axios.post(this.uploadParams.postURL,
|
||||
formData,
|
||||
|
|
@ -639,6 +670,8 @@ export default {
|
|||
this.closeAction()
|
||||
}).catch(e => {
|
||||
this.$notifyError(e)
|
||||
}).finally(() => {
|
||||
this.uploading = false
|
||||
})
|
||||
},
|
||||
fetchCustomHypervisorName () {
|
||||
|
|
@ -1124,12 +1157,18 @@ export default {
|
|||
duration: 0
|
||||
})
|
||||
}
|
||||
api('getUploadParamsForTemplate', params).then(json => {
|
||||
api('getUploadParamsForTemplate', params).then(async json => {
|
||||
this.uploadParams = (json.postuploadtemplateresponse && json.postuploadtemplateresponse.getuploadparams) ? json.postuploadtemplateresponse.getuploadparams : ''
|
||||
this.handleUpload()
|
||||
if (this.userdataid !== null) {
|
||||
this.linkUserdataToTemplate(this.userdataid, json.postuploadtemplateresponse.template[0].id)
|
||||
}
|
||||
this.ssvmOrigin = new URL(this.uploadParams.postURL).origin
|
||||
const trusted = await probeSsvmCert(this.ssvmOrigin)
|
||||
if (!trusted) {
|
||||
this.ssvmCertUntrusted = true
|
||||
return
|
||||
}
|
||||
this.handleUpload()
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
}).finally(() => {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,29 @@
|
|||
// under the License.
|
||||
|
||||
<template>
|
||||
<div class="form-layout" v-ctrl-enter="handleSubmit">
|
||||
<span v-if="uploadPercentage > 0">
|
||||
<div class="form-layout">
|
||||
<span v-if="uploading">
|
||||
<loading-outlined />
|
||||
{{ $t('message.upload.file.processing') }}
|
||||
<a-progress :percent="uploadPercentage" />
|
||||
</span>
|
||||
<a-spin :spinning="loading" v-else>
|
||||
<div v-else-if="ssvmCertUntrusted" class="ssvm-cert-warning">
|
||||
<a-alert
|
||||
type="warning"
|
||||
show-icon
|
||||
:message="$t('message.ssvm.cert.untrusted')"
|
||||
:description="$t('message.ssvm.cert.trust.instructions')" />
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button :href="ssvmOrigin" target="_blank" rel="noopener noreferrer">
|
||||
{{ $t('label.ssvm.open.cert.page') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="retryUpload">
|
||||
{{ $t('label.retry.upload') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-spin :spinning="loading" v-else v-ctrl-enter="handleSubmit">
|
||||
<a-form
|
||||
:ref="formRef"
|
||||
:model="form"
|
||||
|
|
@ -156,6 +172,7 @@ import { ref, reactive, toRaw } from 'vue'
|
|||
import { api } from '@/api'
|
||||
import { axios } from '../../utils/request'
|
||||
import { mixinForm } from '@/utils/mixin'
|
||||
import { probeSsvmCert } from '@/utils/ssvmProbe'
|
||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue'
|
||||
|
|
@ -178,7 +195,10 @@ export default {
|
|||
customDiskOffering: false,
|
||||
isCustomizedDiskIOps: false,
|
||||
loading: false,
|
||||
uploadPercentage: 0
|
||||
uploading: false,
|
||||
uploadPercentage: 0,
|
||||
ssvmCertUntrusted: false,
|
||||
ssvmOrigin: ''
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
|
|
@ -267,6 +287,63 @@ export default {
|
|||
this.form.account = accountName
|
||||
this.account = accountName
|
||||
},
|
||||
async retryUpload () {
|
||||
this.loading = true
|
||||
const reachable = await probeSsvmCert(this.ssvmOrigin)
|
||||
this.loading = false
|
||||
if (!reachable) {
|
||||
this.$message.warning(this.$t('message.ssvm.unreachable.retry'))
|
||||
return
|
||||
}
|
||||
this.ssvmCertUntrusted = false
|
||||
this.handleUpload()
|
||||
},
|
||||
handleUpload () {
|
||||
if (this.fileList.length > 1) {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.upload.volume.failed'),
|
||||
description: this.$t('message.upload.file.limit'),
|
||||
duration: 0
|
||||
})
|
||||
return
|
||||
}
|
||||
const { fileList } = this
|
||||
const formData = new FormData()
|
||||
fileList.forEach(file => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploading = true
|
||||
this.uploadPercentage = 0
|
||||
axios.post(this.uploadParams.postURL,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data',
|
||||
'x-signature': this.uploadParams.signature,
|
||||
'x-expires': this.uploadParams.expires,
|
||||
'x-metadata': this.uploadParams.metadata
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
this.uploadPercentage = Number(parseFloat(100 * progressEvent.loaded / progressEvent.total).toFixed(1))
|
||||
},
|
||||
timeout: 86400000
|
||||
}).then((json) => {
|
||||
this.$notification.success({
|
||||
message: this.$t('message.success.upload'),
|
||||
description: this.$t('message.success.upload.volume.description')
|
||||
})
|
||||
this.closeAction()
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.upload.failed'),
|
||||
description: `${this.$t('message.upload.volume.failed')} - ${e}`,
|
||||
duration: 0
|
||||
})
|
||||
}).finally(() => {
|
||||
this.uploading = false
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
|
|
@ -286,49 +363,15 @@ export default {
|
|||
}
|
||||
params.domainId = this.domainId
|
||||
this.loading = true
|
||||
api('getUploadParamsForVolume', params).then(json => {
|
||||
api('getUploadParamsForVolume', params).then(async json => {
|
||||
this.uploadParams = json.postuploadvolumeresponse?.getuploadparams || ''
|
||||
const { fileList } = this
|
||||
if (this.fileList.length > 1) {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.upload.volume.failed'),
|
||||
description: this.$t('message.upload.file.limit'),
|
||||
duration: 0
|
||||
})
|
||||
this.ssvmOrigin = new URL(this.uploadParams.postURL).origin
|
||||
const trusted = await probeSsvmCert(this.ssvmOrigin)
|
||||
if (!trusted) {
|
||||
this.ssvmCertUntrusted = true
|
||||
return
|
||||
}
|
||||
const formData = new FormData()
|
||||
fileList.forEach(file => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploadPercentage = 0
|
||||
axios.post(this.uploadParams.postURL,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'content-type': 'multipart/form-data',
|
||||
'x-signature': this.uploadParams.signature,
|
||||
'x-expires': this.uploadParams.expires,
|
||||
'x-metadata': this.uploadParams.metadata
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
this.uploadPercentage = Number(parseFloat(100 * progressEvent.loaded / progressEvent.total).toFixed(1))
|
||||
},
|
||||
timeout: 86400000
|
||||
}).then((json) => {
|
||||
this.$notification.success({
|
||||
message: this.$t('message.success.upload'),
|
||||
description: this.$t('message.success.upload.volume.description')
|
||||
})
|
||||
this.closeAction()
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.upload.failed'),
|
||||
description: `${this.$t('message.upload.volume.failed')} - ${e}`,
|
||||
duration: 0
|
||||
})
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.handleUpload()
|
||||
}).catch(e => {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.upload.failed'),
|
||||
|
|
|
|||
Loading…
Reference in New Issue