Skip to content

Commit ff9ee24

Browse files
authored
Fix local upload from browser failing due to ssvm cert not trusted (#13204)
1 parent c267ad3 commit ff9ee24

6 files changed

Lines changed: 208 additions & 57 deletions

File tree

‎ui/public/locales/en.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,8 @@
16171617
"label.offeringid": "Offering ID",
16181618
"label.offeringtype": "Compute Offering type",
16191619
"label.ok": "OK",
1620+
"label.ssvm.open.cert.page": "Open Certificate Page",
1621+
"label.retry.upload": "Retry Upload",
16201622
"label.only.end.date.and.time": "Only end date and time",
16211623
"label.only.start.date.and.time": "Only start date and time",
16221624
"label.open.documentation": "Open documentation",
@@ -3667,6 +3669,9 @@
36673669
"message.upload.iso.failed.description": "Failed to upload ISO.",
36683670
"message.upload.template.failed.description": "Failed to upload Template",
36693671
"message.upload.volume.failed": "Volume upload failed",
3672+
"message.ssvm.cert.untrusted": "Unable to reach the upload server.",
3673+
"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.",
3674+
"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.",
36703675
"message.user.not.permitted.api": "User is not permitted to use the API",
36713676
"message.validate.equalto": "Please enter the same value again.",
36723677
"message.validate.max": "Please enter a value less than or equal to {0}.",

‎ui/src/style/vars.less‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ a {
355355
text-align: right;
356356
padding-top: 15px;
357357

358-
button {
358+
button, a.ant-btn {
359359
margin-right: 5px;
360360
}
361361
}

‎ui/src/utils/ssvmProbe.js‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
const SSVM_PROBE_TIMEOUT_MS = 5000
19+
export async function probeSsvmCert (origin) {
20+
const controller = new AbortController()
21+
const timeoutId = setTimeout(() => controller.abort(), SSVM_PROBE_TIMEOUT_MS)
22+
try {
23+
await fetch(origin, { method: 'HEAD', mode: 'no-cors', signal: controller.signal })
24+
return true
25+
} catch (e) {
26+
return false
27+
} finally {
28+
clearTimeout(timeoutId)
29+
}
30+
}

‎ui/src/views/image/RegisterOrUploadIso.vue‎

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,27 @@
1919
<div
2020
class="form-layout"
2121
@keyup.ctrl.enter="handleSubmit">
22-
<span v-if="uploadPercentage > 0">
22+
<span v-if="uploading">
2323
<loading-outlined />
2424
{{ $t('message.upload.file.processing') }}
2525
<a-progress :percent="uploadPercentage" />
2626
</span>
27+
<div v-else-if="ssvmCertUntrusted" class="ssvm-cert-warning">
28+
<a-alert
29+
type="warning"
30+
show-icon
31+
:message="$t('message.ssvm.cert.untrusted')"
32+
:description="$t('message.ssvm.cert.trust.instructions')" />
33+
<div :span="24" class="action-button">
34+
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
35+
<a-button :href="ssvmOrigin" target="_blank" rel="noopener noreferrer">
36+
{{ $t('label.ssvm.open.cert.page') }}
37+
</a-button>
38+
<a-button type="primary" :loading="loading" @click="retryUpload">
39+
{{ $t('label.retry.upload') }}
40+
</a-button>
41+
</div>
42+
</div>
2743
<a-spin :spinning="loading" v-else>
2844
<a-form
2945
:ref="formRef"
@@ -311,6 +327,7 @@ import { api } from '@/api'
311327
import store from '@/store'
312328
import { axios } from '../../utils/request'
313329
import { mixinForm } from '@/utils/mixin'
330+
import { probeSsvmCert } from '@/utils/ssvmProbe'
314331
import ResourceIcon from '@/components/view/ResourceIcon'
315332
import TooltipLabel from '@/components/widgets/TooltipLabel'
316333
@@ -343,9 +360,12 @@ export default {
343360
userdatapolicy: null,
344361
userdatapolicylist: {},
345362
loading: false,
363+
uploading: false,
346364
allowed: false,
347365
uploadParams: null,
348366
uploadPercentage: 0,
367+
ssvmCertUntrusted: false,
368+
ssvmOrigin: '',
349369
currentForm: ['plus-outlined', 'PlusOutlined'].includes(this.action.currentAction.icon) ? 'Create' : 'Upload',
350370
domains: [],
351371
accounts: [],
@@ -489,6 +509,17 @@ export default {
489509
this.form.file = file
490510
return false
491511
},
512+
async retryUpload () {
513+
this.loading = true
514+
const reachable = await probeSsvmCert(this.ssvmOrigin)
515+
this.loading = false
516+
if (!reachable) {
517+
this.$message.warning(this.$t('message.ssvm.unreachable.retry'))
518+
return
519+
}
520+
this.ssvmCertUntrusted = false
521+
this.handleUpload()
522+
},
492523
handleUpload () {
493524
const { fileList } = this
494525
if (this.fileList.length > 1) {
@@ -502,6 +533,7 @@ export default {
502533
fileList.forEach(file => {
503534
formData.append('files[]', file)
504535
})
536+
this.uploading = true
505537
this.uploadPercentage = 0
506538
axios.post(this.uploadParams.postURL,
507539
formData,
@@ -529,6 +561,8 @@ export default {
529561
description: `${this.$t('message.upload.iso.failed.description')} - ${e}`,
530562
duration: 0
531563
})
564+
}).finally(() => {
565+
this.uploading = false
532566
})
533567
},
534568
handleSubmit (e) {
@@ -583,18 +617,18 @@ export default {
583617
}
584618
params.format = 'ISO'
585619
this.loading = true
586-
api('getUploadParamsForIso', params).then(json => {
620+
api('getUploadParamsForIso', params).then(async json => {
587621
this.uploadParams = (json.postuploadisoresponse && json.postuploadisoresponse.getuploadparams) ? json.postuploadisoresponse.getuploadparams : ''
588-
const response = this.handleUpload()
589622
if (this.userdataid !== null) {
590623
this.linkUserdataToTemplate(this.userdataid, json.postuploadisoresponse.iso[0].id)
591624
}
592-
if (response === 'upload successful') {
593-
this.$notification.success({
594-
message: this.$t('message.success.upload'),
595-
description: this.$t('message.success.upload.iso.description')
596-
})
625+
this.ssvmOrigin = new URL(this.uploadParams.postURL).origin
626+
const trusted = await probeSsvmCert(this.ssvmOrigin)
627+
if (!trusted) {
628+
this.ssvmCertUntrusted = true
629+
return
597630
}
631+
this.handleUpload()
598632
}).catch(error => {
599633
this.$notifyError(error)
600634
}).finally(() => {

‎ui/src/views/image/RegisterOrUploadTemplate.vue‎

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,27 @@
1919
<div
2020
:class="'form-layout'"
2121
@keyup.ctrl.enter="handleSubmit">
22-
<span v-if="uploadPercentage > 0">
22+
<span v-if="uploading">
2323
<loading-outlined />
2424
{{ $t('message.upload.file.processing') }}
2525
<a-progress :percent="uploadPercentage" />
2626
</span>
27+
<div v-else-if="ssvmCertUntrusted" class="ssvm-cert-warning">
28+
<a-alert
29+
type="warning"
30+
show-icon
31+
:message="$t('message.ssvm.cert.untrusted')"
32+
:description="$t('message.ssvm.cert.trust.instructions')" />
33+
<div :span="24" class="action-button">
34+
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
35+
<a-button :href="ssvmOrigin" target="_blank" rel="noopener noreferrer">
36+
{{ $t('label.ssvm.open.cert.page') }}
37+
</a-button>
38+
<a-button type="primary" :loading="loading" @click="retryUpload">
39+
{{ $t('label.retry.upload') }}
40+
</a-button>
41+
</div>
42+
</div>
2743
<a-spin :spinning="loading" v-else>
2844
<a-form
2945
:ref="formRef"
@@ -472,6 +488,7 @@ import { api } from '@/api'
472488
import store from '@/store'
473489
import { axios } from '../../utils/request'
474490
import { mixinForm } from '@/utils/mixin'
491+
import { probeSsvmCert } from '@/utils/ssvmProbe'
475492
import ResourceIcon from '@/components/view/ResourceIcon'
476493
import TooltipLabel from '@/components/widgets/TooltipLabel'
477494
@@ -497,6 +514,8 @@ export default {
497514
uploadPercentage: 0,
498515
uploading: false,
499516
fileList: [],
517+
ssvmCertUntrusted: false,
518+
ssvmOrigin: '',
500519
zones: {},
501520
defaultZone: '',
502521
hyperVisor: {},
@@ -610,12 +629,24 @@ export default {
610629
this.form.file = file
611630
return false
612631
},
632+
async retryUpload () {
633+
this.loading = true
634+
const reachable = await probeSsvmCert(this.ssvmOrigin)
635+
this.loading = false
636+
if (!reachable) {
637+
this.$message.warning(this.$t('message.ssvm.unreachable.retry'))
638+
return
639+
}
640+
this.ssvmCertUntrusted = false
641+
this.handleUpload()
642+
},
613643
handleUpload () {
614644
const { fileList } = this
615645
const formData = new FormData()
616646
fileList.forEach(file => {
617647
formData.append('files[]', file)
618648
})
649+
this.uploading = true
619650
this.uploadPercentage = 0
620651
axios.post(this.uploadParams.postURL,
621652
formData,
@@ -639,6 +670,8 @@ export default {
639670
this.closeAction()
640671
}).catch(e => {
641672
this.$notifyError(e)
673+
}).finally(() => {
674+
this.uploading = false
642675
})
643676
},
644677
fetchCustomHypervisorName () {
@@ -1124,12 +1157,18 @@ export default {
11241157
duration: 0
11251158
})
11261159
}
1127-
api('getUploadParamsForTemplate', params).then(json => {
1160+
api('getUploadParamsForTemplate', params).then(async json => {
11281161
this.uploadParams = (json.postuploadtemplateresponse && json.postuploadtemplateresponse.getuploadparams) ? json.postuploadtemplateresponse.getuploadparams : ''
1129-
this.handleUpload()
11301162
if (this.userdataid !== null) {
11311163
this.linkUserdataToTemplate(this.userdataid, json.postuploadtemplateresponse.template[0].id)
11321164
}
1165+
this.ssvmOrigin = new URL(this.uploadParams.postURL).origin
1166+
const trusted = await probeSsvmCert(this.ssvmOrigin)
1167+
if (!trusted) {
1168+
this.ssvmCertUntrusted = true
1169+
return
1170+
}
1171+
this.handleUpload()
11331172
}).catch(error => {
11341173
this.$notifyError(error)
11351174
}).finally(() => {

0 commit comments

Comments
 (0)