| // 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. |
| |
| <template> |
| <div> |
| <a-card class="ant-form-text" style="text-align: justify; margin: 10px 0; padding: 15px;" v-html="$t('message.desc.register.template')" /> |
| <a-table |
| :columns="columns" |
| :dataSource="predefinedTemplates" |
| :rowSelection="rowSelection" |
| :loading="loading" |
| :scroll="{ y: 450 }" |
| size="middle" |
| :rowKey="record => record.id" |
| :pagination="false" |
| class="form-content" |
| > |
| <template #bodyCell="{ column, record }"> |
| <template v-if="column.key === 'name'"> |
| <os-logo :osName="record.name" size="xl" /> |
| {{ record.name }} |
| </template> |
| </template> |
| </a-table> |
| <div class="form-action"> |
| <a-button class="button-back" @click="handleDone">{{ selectedRowKeys.length > 0 ? $t('label.done') : $t('label.skip') }}</a-button> |
| <a-button class="button-next" type="primary" @click="handleSubmit" ref="submit">{{ $t('label.register.template') }}</a-button> |
| </div> |
| |
| <a-modal |
| :visible="showAlert" |
| :footer="null" |
| style="top: 20px;" |
| centered |
| width="auto" |
| @cancel="showAlert = false" |
| > |
| <template #title> |
| {{ $t('label.warning') }} |
| </template> |
| <a-alert type="warning"> |
| <template #message> |
| <span v-html="$t('message.warn.select.template')" /> |
| </template> |
| </a-alert> |
| <a-divider style="margin-top: 0;"></a-divider> |
| </a-modal> |
| </div> |
| </template> |
| |
| <script> |
| import { getAPI, postAPI } from '@/api' |
| import { genericCompare } from '@/utils/sort.js' |
| import OsLogo from '@/components/widgets/OsLogo' |
| |
| export default { |
| name: 'ZoneWizardRegisterTemplate', |
| components: { |
| OsLogo |
| }, |
| props: { |
| zoneid: { |
| type: String, |
| required: false |
| }, |
| arch: { |
| type: String, |
| required: false |
| }, |
| zoneSuperType: { |
| type: String, |
| required: false |
| } |
| }, |
| data: () => ({ |
| columns: null, |
| loading: false, |
| predefinedTemplates: [], |
| rowKey: 0, |
| selectedRowKeys: [], |
| defaultOsTypeId: null, |
| deployedTemplates: {}, |
| showAlert: false |
| }), |
| created () { |
| this.initForm() |
| }, |
| mounted () { |
| this.fetchPredefinedTemplates() |
| }, |
| computed: { |
| rowSelection () { |
| return { |
| selectedRowKeys: this.selectedRowKeys || [], |
| onChange: this.onSelectRow, |
| getCheckboxProps: (record) => { |
| return { |
| disabled: record.disabled |
| } |
| } |
| } |
| } |
| }, |
| methods: { |
| async initForm () { |
| this.columns = [ |
| { |
| title: 'Name', |
| dataIndex: 'name', |
| key: 'name', |
| width: 240, |
| sorter: (a, b) => { return genericCompare(a.name || '', b.name || '') } |
| }, |
| { |
| title: 'Arch', |
| dataIndex: 'arch', |
| key: 'arch', |
| width: 80, |
| sorter: (a, b) => { return genericCompare(a.arch || '', b.arch || '') } |
| }, |
| { |
| title: 'URL', |
| dataIndex: 'url', |
| key: 'url' |
| } |
| ] |
| this.defaultOsTypeId = await this.fetchOsTypeId('Other Linux (64-bit)') |
| }, |
| handleDone () { |
| this.$emit('refresh-data') |
| }, |
| async handleSubmit () { |
| await this.stepRegisterTemplates() |
| }, |
| onSelectRow (value) { |
| this.selectedRowKeys = value |
| }, |
| async registerTemplate (templateData) { |
| const params = { |
| displaytext: templateData.name + ' ' + templateData.arch, |
| format: this.getImageFormat(templateData.url), |
| hypervisor: 'KVM', |
| name: templateData.name, |
| arch: templateData.arch, |
| url: templateData.url, |
| ostypeid: await this.fetchOsTypeId(templateData.name), |
| zoneid: this.zoneid |
| } |
| if (this.zoneSuperType === 'Edge') { |
| params.directdownload = true |
| } |
| return new Promise((resolve, reject) => { |
| postAPI('registerTemplate', params).then(json => { |
| const result = json.registertemplateresponse.template[0] |
| resolve(result) |
| }).catch(error => { |
| const message = error.response.headers['x-description'] |
| reject(message) |
| }) |
| }) |
| }, |
| async stepRegisterTemplates () { |
| const templatesToRegister = this.predefinedTemplates.filter(template => this.selectedRowKeys.includes(template.id) && this.deployedTemplates[template.id] !== true) |
| if (templatesToRegister.length === 0) { |
| this.showAlert = true |
| return |
| } |
| const registrationResults = [] |
| for (const templateData of templatesToRegister) { |
| const promise = this.registerTemplate(templateData) |
| .then(() => ({ |
| id: templateData.id, |
| status: 'success', |
| name: templateData.name |
| })) |
| .catch(() => ({ |
| id: templateData.id, |
| status: 'error', |
| name: templateData.name |
| })) |
| registrationResults.push(promise) |
| } |
| const results = await Promise.all(registrationResults) |
| const successful = results.filter(r => r.status === 'success') |
| const failed = results.filter(r => r.status === 'error') |
| |
| if (successful.length > 0) { |
| this.$notification.success({ |
| message: this.$t('label.register.template'), |
| description: 'Successfully registered templates: ' + successful.map(r => r.name).join(', ') |
| }) |
| |
| successful.forEach(r => { |
| this.deployedTemplates[r.id] = true |
| this.predefinedTemplates.find(t => t.id === r.id).disabled = true |
| }) |
| } |
| if (failed.length > 0) { |
| this.$notification.error({ |
| message: this.$t('label.register.template'), |
| description: 'Failed registering templates: ' + failed.map(r => r.name).join(', ') |
| }) |
| |
| failed.forEach(r => { |
| this.predefinedTemplates.find(t => t.id === r.id).disabled = true |
| this.selectedRowKeys = this.selectedRowKeys.filter(id => id !== r.id) |
| }) |
| } |
| }, |
| async fetchOsTypeId (osName) { |
| let osTypeId = this.defaultOsTypeId |
| this.loading = true |
| try { |
| const json = await getAPI('listOsTypes', { keyword: osName, filter: 'name,id' }) |
| if (json && json.listostypesresponse && json.listostypesresponse.ostype && json.listostypesresponse.ostype.length > 0) { |
| osTypeId = json.listostypesresponse.ostype[0].id |
| } |
| } catch (error) { |
| console.error('Error fetching OS types:', error) |
| } finally { |
| this.loading = false |
| } |
| return osTypeId |
| }, |
| getImageFormat (url) { |
| const fileExtension = url.split('.').pop() |
| var format = fileExtension |
| switch (fileExtension) { |
| case 'img': |
| format = 'RAW' |
| break |
| case 'qcow2': |
| format = 'qcow2' |
| break |
| default: |
| format = 'RAW' |
| } |
| return format |
| }, |
| async fetchPredefinedTemplates () { |
| this.loading = true |
| try { |
| const response = await fetch('./cloud-image-templates.json') |
| if (!response.ok) { |
| throw new Error(`Error fetching predefined templates, status_code: ${response.status}`) |
| } |
| const templates = await response.json() |
| this.predefinedTemplates = this.arch |
| ? templates.filter(template => template.arch === this.arch) |
| : templates |
| |
| // Replace 'https' with 'http' in all URLs for EdgeZone |
| if (this.zoneSuperType === 'Edge') { |
| this.predefinedTemplates.forEach(template => { |
| if (template.url.startsWith('https://')) { |
| template.url = template.url.replace('https://', 'http://') |
| } |
| }) |
| } |
| } catch (error) { |
| console.error('Error fetching predefined templates:', error) |
| this.predefinedTemplates = [] |
| } finally { |
| this.loading = false |
| } |
| } |
| } |
| } |
| </script> |
| |
| <style lang="less" scoped> |
| .form-content { |
| border: 1px dashed #e9e9e9; |
| border-radius: 6px; |
| background-color: #fafafa; |
| min-height: 440px; |
| text-align: center; |
| vertical-align: center; |
| padding: 8px; |
| padding-top: 16px; |
| margin-top: 8px; |
| overflow-y: auto; |
| } |
| </style> |